| // Copyright 2013 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "components/autofill/ios/browser/autofill_driver_ios.h" |
| |
| #import <concepts> |
| #import <functional> |
| #import <optional> |
| #import <type_traits> |
| #import <utility> |
| #import <variant> |
| |
| #import "base/check_deref.h" |
| #import "base/containers/contains.h" |
| #import "base/containers/to_vector.h" |
| #import "base/feature_list.h" |
| #import "base/functional/bind.h" |
| #import "base/memory/ptr_util.h" |
| #import "base/memory/raw_ptr.h" |
| #import "base/memory/weak_ptr.h" |
| #import "base/metrics/histogram.h" |
| #import "base/metrics/histogram_functions.h" |
| #import "base/notimplemented.h" |
| #import "base/observer_list.h" |
| #import "components/autofill/core/browser/autofill_field.h" |
| #import "components/autofill/core/browser/filling/form_filler.h" |
| #import "components/autofill/core/browser/form_structure.h" |
| #import "components/autofill/core/browser/foundations/autofill_driver_router.h" |
| #import "components/autofill/core/common/autofill_features.h" |
| #import "components/autofill/core/common/field_data_manager.h" |
| #import "components/autofill/core/common/mojom/autofill_types.mojom-shared.h" |
| #import "components/autofill/core/common/unique_ids.h" |
| #import "components/autofill/ios/browser/autofill_client_ios.h" |
| #import "components/autofill/ios/browser/autofill_driver_ios_bridge.h" |
| #import "components/autofill/ios/browser/autofill_driver_ios_factory.h" |
| #import "components/autofill/ios/browser/autofill_java_script_feature.h" |
| #import "components/autofill/ios/browser/autofill_util.h" |
| #import "components/autofill/ios/browser/form_fetch_batcher.h" |
| #import "components/autofill/ios/common/features.h" |
| #import "components/autofill/ios/common/field_data_manager_factory_ios.h" |
| #import "components/autofill/ios/form_util/child_frame_registrar.h" |
| #import "components/ukm/ios/ukm_url_recorder.h" |
| #import "ios/web/public/browser_state.h" |
| #import "ios/web/public/js_messaging/content_world.h" |
| #import "ios/web/public/js_messaging/web_frame.h" |
| #import "ios/web/public/js_messaging/web_frames_manager.h" |
| #import "ios/web/public/web_state.h" |
| #import "services/network/public/cpp/shared_url_loader_factory.h" |
| #import "url/origin.h" |
| |
| namespace autofill { |
| |
| namespace { |
| |
| // WithNewVersion() bumps the FormData::version of each form. This should be |
| // called for every browser form before it enters AutofillManager so that |
| // AutofillManager can distinguish newer and older forms. |
| // |
| // TODO(crbug.com/40144964): Use this in all renderer -> browser communications. |
| // TODO(crbug.com/40144964): Remove once FormData objects aren't stored |
| // globally anymore. |
| |
| // No-op: add types to the `requires` clause below as necessary. |
| template <typename T> |
| requires(std::is_scalar_v<std::remove_cvref_t<T>>) |
| T&& WithNewVersion(T&& x) { |
| return std::forward<T>(x); |
| } |
| |
| auto& WithNewVersion(const FormData& browser_form) { |
| static FormVersion version_counter; |
| ++*version_counter; |
| // This const_cast is a hack to avoid additional copies. It's OK because the |
| // FormData is owned by AutofillDriverRouter, FormData::version is written |
| // only here and read only in AutofillManager. |
| const_cast<FormData&>(browser_form).set_version(version_counter); |
| return browser_form; |
| } |
| |
| auto& WithNewVersion(const std::optional<FormData>& browser_form) { |
| if (browser_form) { |
| WithNewVersion(*browser_form); |
| } |
| return browser_form; |
| } |
| |
| template <typename... Args> |
| base::OnceCallback<void(Args...)> WithNewVersion( |
| base::OnceCallback<void(Args...)> cb) { |
| return base::BindOnce( |
| [](base::OnceCallback<void(Args...)> cb, Args... args) { |
| std::move(cb).Run(WithNewVersion(std::forward<Args>(args))...); |
| }, |
| std::move(cb)); |
| } |
| |
| // AutofillDriverIOS::router_ only ever routes between instances of |
| // AutofillDriverIOS, so this cast is safe. |
| AutofillDriverIOS* cast(AutofillDriver* driver) { |
| return static_cast<AutofillDriverIOS*>(driver); |
| } |
| |
| bool IsAcrossIframesEnabled() { |
| return base::FeatureList::IsEnabled( |
| autofill::features::kAutofillAcrossIframesIos); |
| } |
| |
| base::TimeDelta GetDocumentFormScanPeriod() { |
| return base::Milliseconds(kAutofillDocumentFormScanPeriodMs.Get()); |
| } |
| |
| base::TimeDelta GetFilteredDocumentFormScanPeriod() { |
| return base::Milliseconds(kAutofillFilteredDocumentFormScanPeriodMs.Get()); |
| } |
| |
| bool UseXhrFix() { |
| return base::FeatureList::IsEnabled(kAutofillFixXhrForXframe); |
| } |
| |
| } // namespace |
| |
| // static |
| AutofillDriverIOS* AutofillDriverIOS::FromWebStateAndWebFrame( |
| web::WebState* web_state, |
| web::WebFrame* web_frame) { |
| if (AutofillClientIOS* client = AutofillClientIOS::FromWebState(web_state)) { |
| return client->GetAutofillDriverFactory().DriverForFrame(web_frame); |
| } |
| return nullptr; |
| } |
| |
| // static |
| AutofillDriverIOS* AutofillDriverIOS::FromWebStateAndLocalFrameToken( |
| web::WebState* web_state, |
| LocalFrameToken token) { |
| web::WebFramesManager* frames_manager = |
| AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(web_state); |
| web::WebFrame* frame = frames_manager->GetFrameWithId(token.ToString()); |
| return frame ? FromWebStateAndWebFrame(web_state, frame) : nullptr; |
| } |
| |
| AutofillDriverIOS::AutofillDriverIOS( |
| web::WebState* web_state, |
| web::WebFrame* web_frame, |
| AutofillClient* client, |
| AutofillDriverRouter* router, |
| id<AutofillDriverIOSBridge> bridge, |
| base::PassKey<AutofillDriverIOSFactory> pass_key) |
| : web_state_(web_state), |
| web_frame_id_(web_frame ? web_frame->GetFrameId() : ""), |
| bridge_(bridge), |
| client_(*client), |
| manager_(std::make_unique<BrowserAutofillManager>(this)), |
| router_(router), |
| document_scan_batcher_(bridge, |
| web_frame ? web_frame->AsWeakPtr() : nullptr, |
| GetDocumentFormScanPeriod()), |
| document_filtered_scan_batcher_( |
| bridge, |
| web_frame ? web_frame->AsWeakPtr() : nullptr, |
| GetFilteredDocumentFormScanPeriod()) { |
| manager_observation_.Observe(manager_.get()); |
| |
| if (IsAcrossIframesEnabled()) { |
| std::optional<base::UnguessableToken> token_temp = |
| DeserializeJavaScriptFrameId(web_frame_id_); |
| if (token_temp) { |
| local_frame_token_ = LocalFrameToken(*token_temp); |
| } |
| } |
| } |
| |
| AutofillDriverIOS::~AutofillDriverIOS() { |
| Unregister(); |
| RecordTriggeredFormExtractionMetrics(); |
| } |
| |
| LocalFrameToken AutofillDriverIOS::GetFrameToken() const { |
| return local_frame_token_; |
| } |
| |
| std::optional<LocalFrameToken> AutofillDriverIOS::Resolve(FrameToken query) { |
| if (!IsAcrossIframesEnabled()) { |
| return std::nullopt; |
| } |
| |
| if (std::holds_alternative<LocalFrameToken>(query)) { |
| return std::get<LocalFrameToken>(query); |
| } |
| CHECK(std::holds_alternative<RemoteFrameToken>(query)); |
| auto remote_token = std::get<RemoteFrameToken>(query); |
| auto* registrar = ChildFrameRegistrar::FromWebState(web_state_); |
| return registrar ? registrar->LookupChildFrame(remote_token) : std::nullopt; |
| } |
| |
| AutofillDriverIOS* AutofillDriverIOS::GetParent() { |
| return parent_.get(); |
| } |
| |
| AutofillClient& AutofillDriverIOS::GetAutofillClient() { |
| return *client_; |
| } |
| |
| BrowserAutofillManager& AutofillDriverIOS::GetAutofillManager() { |
| return *manager_; |
| } |
| |
| ukm::SourceId AutofillDriverIOS::GetPageUkmSourceId() const { |
| return ukm::GetSourceIdForWebStateDocument(web_state_); |
| } |
| |
| // Return true as iOS has no MPArch. |
| bool AutofillDriverIOS::IsActive() const { |
| return true; |
| } |
| |
| bool AutofillDriverIOS::HasSharedAutofillPermission() const { |
| // Give the shared-autofill permission to the main frame of the webstate by |
| // default. |
| if (!web_frame() || web_frame()->IsMainFrame()) { |
| return true; |
| } |
| |
| // Also propagate that permission to the direct children of the main |
| // frame on the same origin as the main frame. |
| if (parent_ && parent_->web_frame() && parent_->web_frame()->IsMainFrame() && |
| web_frame()) { |
| return parent_->web_frame()->GetSecurityOrigin() == |
| web_frame()->GetSecurityOrigin(); |
| } |
| |
| // Return false as share-autofill is not allowed. |
| return false; |
| } |
| |
| bool AutofillDriverIOS::CanShowAutofillUi() const { |
| return true; |
| } |
| |
| base::flat_set<FieldGlobalId> AutofillDriverIOS::ApplyFormAction( |
| mojom::FormActionType action_type, |
| mojom::ActionPersistence action_persistence, |
| base::span<const FormFieldData> fields, |
| const url::Origin& triggered_origin, |
| const base::flat_map<FieldGlobalId, FieldType>& field_type_map, |
| const Section& section_for_clear_form_on_ios) { |
| switch (action_type) { |
| case mojom::FormActionType::kUndo: |
| // TODO(crbug.com/40266549) Add Undo support on iOS. |
| return {}; |
| case mojom::FormActionType::kFill: { |
| auto callback = [§ion_for_clear_form_on_ios]( |
| AutofillDriver& driver, |
| mojom::FormActionType action_type, |
| mojom::ActionPersistence action_persistence, |
| const std::vector<FormFieldData::FillData>& fields) { |
| web::WebFrame* frame = cast(&driver)->web_frame(); |
| if (frame) { |
| [cast(&driver)->bridge_ fillData:fields |
| section:section_for_clear_form_on_ios |
| inFrame:frame]; |
| } |
| }; |
| |
| const url::Origin main_origin = |
| client_->GetLastCommittedPrimaryMainFrameOrigin(); |
| if (IsAcrossIframesEnabled()) { |
| return router_->ApplyFormAction(callback, action_type, |
| action_persistence, fields, main_origin, |
| triggered_origin, field_type_map); |
| } else { |
| callback(*this, action_type, action_persistence, |
| base::ToVector(fields, [](const FormFieldData& field) { |
| return FormFieldData::FillData(field); |
| })); |
| return base::ToVector(fields, &FormFieldData::global_id); |
| } |
| } |
| } |
| } |
| |
| void AutofillDriverIOS::ApplyFieldAction( |
| mojom::FieldActionType action_type, |
| mojom::ActionPersistence action_persistence, |
| const FieldGlobalId& field_id, |
| const std::u16string& value) { |
| auto callback = [](AutofillDriver& driver, mojom::FieldActionType action_type, |
| mojom::ActionPersistence action_persistence, |
| FieldRendererId field, const std::u16string& value) { |
| // For now, only support filling. |
| switch (action_persistence) { |
| case mojom::ActionPersistence::kFill: { |
| [cast(&driver)->bridge_ |
| fillSpecificFormField:field |
| withValue:value |
| inFrame:cast(&driver)->web_frame()]; |
| break; |
| } |
| case mojom::ActionPersistence::kPreview: |
| return; |
| } |
| }; |
| if (IsAcrossIframesEnabled()) { |
| router_->ApplyFieldAction(callback, action_type, action_persistence, |
| field_id, value); |
| } else { |
| callback(*this, action_type, action_persistence, field_id.renderer_id, |
| value); |
| } |
| } |
| |
| void AutofillDriverIOS::ExtractForm( |
| FormGlobalId form_id, |
| base::OnceCallback<void(AutofillDriver*, const std::optional<FormData>&)> |
| final_handler) { |
| if (!web_frame()) { |
| std::move(final_handler).Run(nullptr, std::nullopt); |
| return; |
| } |
| |
| if (IsAcrossIframesEnabled()) { |
| // TODO(crbug.com/455870070): Introduce a `fetchForm` method in the agent |
| // that would extract a single form only given the renderer id and replace |
| // the call to `fetchFormsFiltered()` with it. |
| router_->ExtractForm( |
| [](autofill::AutofillDriver& request_target, |
| FormRendererId form_renderer_id, |
| AutofillDriverRouter::RendererFormHandler renderer_form_handler) { |
| auto completion_handler = base::BindOnce( |
| [&](FormRendererId form_renderer_id, |
| AutofillDriverRouter::RendererFormHandler |
| renderer_form_handler, |
| std::optional<std::vector<FormData>> forms) { |
| if (!forms) { |
| std::move(renderer_form_handler).Run(std::nullopt); |
| return; |
| } |
| auto it = std::ranges::find(*forms, form_renderer_id, |
| &FormData::renderer_id); |
| std::move(renderer_form_handler) |
| .Run(it == forms->end() ? std::nullopt |
| : std::optional(std::move(*it))); |
| }, |
| form_renderer_id, std::move(renderer_form_handler)); |
| |
| auto& source = static_cast<AutofillDriverIOS&>(request_target); |
| [source.bridge_ fetchFormsFiltered:NO |
| withName:std::u16string() |
| inFrame:source.web_frame() |
| completionHandler:std::move(completion_handler)]; |
| }, |
| form_id, WithNewVersion(std::move(final_handler))); |
| } else { |
| std::move(final_handler).Run(nullptr, std::nullopt); |
| } |
| } |
| |
| void AutofillDriverIOS::ExposeDomNodeIdsInAllFrames() {} |
| |
| void AutofillDriverIOS::SendTypePredictionsToRenderer( |
| const FormStructure& form) { |
| CHECK(base::FeatureList::IsEnabled( |
| features::test::kAutofillShowTypePredictions)); |
| auto callback = [](AutofillDriver& driver, |
| const std::vector<FormDataPredictions>& preds) { |
| web::WebFrame* frame = cast(&driver)->web_frame(); |
| if (!frame) { |
| return; |
| } |
| [cast(&driver)->bridge_ fillFormDataPredictions:preds inFrame:frame]; |
| }; |
| |
| if (IsAcrossIframesEnabled()) { |
| router_->SendTypePredictionsToRenderer(callback, |
| form.GetFieldTypePredictions()); |
| } else { |
| callback(*this, {form.GetFieldTypePredictions()}); |
| } |
| } |
| |
| void AutofillDriverIOS::RendererShouldAcceptDataListSuggestion( |
| const FieldGlobalId& field_id, |
| const std::u16string& value) {} |
| |
| void AutofillDriverIOS::TriggerFormExtractionInDriverFrame( |
| AutofillDriverRouterAndFormForestPassKey pass_key) { |
| if (!is_processed()) { |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kAutofillAcrossIframesIosTriggerFormExtraction)) { |
| ScanForms(); |
| } |
| } |
| |
| void AutofillDriverIOS::ScanForms(bool immediately) { |
| if (!web_frame()) { |
| return; |
| } |
| |
| const auto callback = |
| [](id<AutofillDriverIOSBridge> bridge, base::WeakPtr<web::WebFrame> frame, |
| std::optional<std::vector<autofill::FormData>> forms) { |
| if (!frame || !forms || forms->empty()) { |
| return; |
| } |
| [bridge notifyFormsSeen:*std::move(forms) inFrame:frame.get()]; |
| }; |
| |
| if (base::FeatureList::IsEnabled(kAutofillThrottleDocumentFormScanIos)) { |
| immediately ? document_scan_batcher_.PushRequestAndRun(base::BindOnce( |
| callback, bridge_, web_frame()->AsWeakPtr())) |
| : document_scan_batcher_.PushRequest(base::BindOnce( |
| callback, bridge_, web_frame()->AsWeakPtr())); |
| } else { |
| [bridge_ fetchFormsFiltered:NO |
| withName:std::u16string() |
| inFrame:web_frame() |
| completionHandler:base::BindOnce(callback, bridge_, |
| web_frame()->AsWeakPtr())]; |
| } |
| } |
| |
| void AutofillDriverIOS::FetchFormsFilteredByName( |
| const std::u16string& form_name, |
| FormFetchCompletion completion) { |
| if (!web_frame()) { |
| return; |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| kAutofillThrottleFilteredDocumentFormScanIos)) { |
| document_filtered_scan_batcher_.PushRequest(std::move(completion), |
| form_name); |
| } else { |
| [bridge_ fetchFormsFiltered:YES |
| withName:form_name |
| inFrame:web_frame() |
| completionHandler:std::move(completion)]; |
| } |
| } |
| |
| void AutofillDriverIOS::TriggerFormExtractionInAllFrames( |
| base::OnceCallback<void(bool)> form_extraction_finished_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void AutofillDriverIOS::GetFourDigitCombinationsFromDom( |
| base::OnceCallback<void(const std::vector<std::string>&)> |
| potential_matches) { |
| // TODO(crbug.com/40260122): Implement GetFourDigitCombinationsFromDom() in |
| // iOS. |
| NOTIMPLEMENTED(); |
| } |
| |
| void AutofillDriverIOS::ExtractLabeledTextNodeValue( |
| const std::u16string& value_regex, |
| const std::u16string& label_regex, |
| uint32_t number_of_ancestor_levels_to_search, |
| base::OnceCallback<void(const std::string& amount)> response_callback) { |
| NOTIMPLEMENTED(); |
| } |
| |
| void AutofillDriverIOS::RendererShouldClearPreviewedForm() {} |
| |
| void AutofillDriverIOS::RendererShouldTriggerSuggestions( |
| const FieldGlobalId& field_id, |
| AutofillSuggestionTriggerSource trigger_source) { |
| // Triggering suggestions from the browser process is currently only used for |
| // manual fallbacks on Desktop. It is not implemented on iOS. |
| NOTIMPLEMENTED(); |
| } |
| |
| void AutofillDriverIOS::RendererShouldSetSuggestionAvailability( |
| const FieldGlobalId& field_id, |
| mojom::AutofillSuggestionAvailability suggestion_availability) {} |
| |
| std::optional<net::IsolationInfo> AutofillDriverIOS::GetIsolationInfo() { |
| // On iOS we have a single, shared URLLoaderFactory provided by BrowserState. |
| // As it is shared, it is not trusted and we cannot assign trusted_params |
| // to the network request. On iOS, the IsolationInfo should always be nullopt. |
| return std::nullopt; |
| } |
| |
| web::WebFrame* AutofillDriverIOS::web_frame() const { |
| web::WebFramesManager* frames_manager = |
| AutofillJavaScriptFeature::GetInstance()->GetWebFramesManager(web_state_); |
| return frames_manager->GetFrameWithId(web_frame_id_); |
| } |
| |
| void AutofillDriverIOS::AskForValuesToFill(const FormData& form, |
| const FieldGlobalId& field_id) { |
| auto callback = |
| [](AutofillDriver& driver, const FormData& form, |
| const FieldGlobalId& field_id, const gfx::Rect& bounding_box, |
| AutofillSuggestionTriggerSource trigger_source, |
| std::optional<PasswordSuggestionRequest> password_request) { |
| driver.GetAutofillManager().OnAskForValuesToFill( |
| form, field_id, bounding_box, trigger_source, |
| std::move(password_request)); |
| }; |
| // The caret position is currently not extracted on iOS. |
| gfx::Rect caret_bounds; |
| if (IsAcrossIframesEnabled()) { |
| // TODO(crbug.com/40269303): Distinguish between different trigger sources. |
| router_->AskForValuesToFill(callback, *this, form, field_id, caret_bounds, |
| autofill::AutofillSuggestionTriggerSource::kiOS, |
| std::nullopt); |
| } else { |
| callback(*this, form, field_id, caret_bounds, |
| autofill::AutofillSuggestionTriggerSource::kiOS, std::nullopt); |
| } |
| } |
| |
| void AutofillDriverIOS::DidAutofillForm(const FormData& form) { |
| if (UseXhrFix()) { |
| // Update the last_interacted_form_ locally in the renderer frame before |
| // routing so XHR detection can be done when a renderer form is deleted. |
| UpdateLastInteractedForm(/*form_data=*/form); |
| } |
| auto callback = [](AutofillDriver& driver, const FormData& form) { |
| if (!UseXhrFix()) { |
| cast(&driver)->UpdateLastInteractedForm(/*form_data=*/form); |
| } |
| driver.GetAutofillManager().OnDidAutofillForm(form); |
| }; |
| if (IsAcrossIframesEnabled()) { |
| router_->DidAutofillForm(callback, *this, form); |
| } else { |
| callback(*this, form); |
| } |
| } |
| |
| void AutofillDriverIOS::FormsSeen( |
| const std::vector<FormData>& updated_forms, |
| const std::vector<FormGlobalId>& removed_forms) { |
| auto callback = [](AutofillDriver& driver, |
| const std::vector<FormData>& updated_forms, |
| const std::vector<FormGlobalId>& removed_forms) { |
| driver.GetAutofillManager().OnFormsSeen(updated_forms, removed_forms); |
| }; |
| |
| if (IsAcrossIframesEnabled()) { |
| // Any RemoteFrameTokens encountered for the first time should be posted to |
| // the registrar, which allows this driver to be established as the parent |
| // of the child frame. |
| for (const autofill::FormData& form : updated_forms) { |
| for (const autofill::FrameTokenWithPredecessor& child_frame : |
| form.child_frames()) { |
| // This std::get is safe because on iOS, FormData::child_frames is |
| // only ever populated with RemoteFrameTokens. std::get will fail a |
| // CHECK if this assumption is ever wrong. |
| auto token = std::get<autofill::RemoteFrameToken>(child_frame.token); |
| auto* registrar = |
| ChildFrameRegistrar::GetOrCreateForWebState(web_state_); |
| if (registrar && known_child_frames_.insert(token).second) { |
| registrar->DeclareNewRemoteToken( |
| token, base::BindOnce(&AutofillDriverIOS::SetSelfAsParent, |
| weak_ptr_factory_.GetWeakPtr(), form)); |
| } |
| } |
| } |
| router_->FormsSeen(callback, *this, updated_forms, removed_forms); |
| } else { |
| callback(*this, updated_forms, removed_forms); |
| } |
| } |
| |
| void AutofillDriverIOS::FormSubmitted( |
| const FormData& form, |
| mojom::SubmissionSource submission_source) { |
| auto callback = [webstate_ptr = web_state_]( |
| AutofillDriver& driver, const FormData& form, |
| mojom::SubmissionSource submission_source) { |
| CHECK(webstate_ptr); |
| base::UmaHistogramEnumeration(kAutofillSubmissionDetectionSourceHistogram, |
| submission_source); |
| driver.GetAutofillManager().OnFormSubmitted(form, submission_source); |
| if (UseXhrFix()) { |
| // Clear the last interacted form on the child frames to not trigger |
| // XHR again in case there were some interactions with forms in these |
| // frames prior to submission. This is to avoid spamming submits. |
| for (const auto& remote_token : form.child_frames()) { |
| if (std::optional<LocalFrameToken> local_token = |
| driver.Resolve(remote_token.token)) { |
| if (AutofillDriverIOS* child_driver = |
| FromWebStateAndLocalFrameToken(webstate_ptr, *local_token)) { |
| child_driver->ClearLastInteractedForm(); |
| } |
| } |
| } |
| } |
| cast(&driver)->ClearLastInteractedForm(); |
| }; |
| if (IsAcrossIframesEnabled()) { |
| router_->FormSubmitted(callback, *this, form, submission_source); |
| } else { |
| callback(*this, form, submission_source); |
| } |
| if (UseXhrFix()) { |
| ClearLastInteractedForm(); |
| } |
| } |
| |
| void AutofillDriverIOS::CaretMovedInFormField(const FormData& form, |
| const FieldGlobalId& field_id, |
| const gfx::Rect& caret_bounds) { |
| GetAutofillManager().OnCaretMovedInFormField(form, field_id, caret_bounds); |
| } |
| |
| void AutofillDriverIOS::TextFieldValueChanged(const FormData& form, |
| const FieldGlobalId& field_id, |
| base::TimeTicks timestamp) { |
| if (UseXhrFix()) { |
| // Update the last_interacted_form_ locally in the renderer frame before |
| // routing so XHR detection can be done when a renderer form is deleted. |
| UpdateLastInteractedForm( |
| /*form_data=*/form, |
| /*formless_field=*/form.renderer_id() ? FieldRendererId() |
| : field_id.renderer_id); |
| } |
| auto callback = [&](AutofillDriver& driver, const FormData& form, |
| const FieldGlobalId& field_global_id, |
| base::TimeTicks timestamp) { |
| if (!UseXhrFix()) { |
| cast(&driver)->UpdateLastInteractedForm( |
| /*form_data=*/form, |
| /*formless_field=*/form.renderer_id() ? FieldRendererId() |
| : field_global_id.renderer_id); |
| } |
| driver.GetAutofillManager().OnTextFieldValueChanged(form, field_id, |
| timestamp); |
| }; |
| |
| if (IsAcrossIframesEnabled()) { |
| router_->TextFieldValueChanged(callback, *this, form, field_id, timestamp); |
| } else { |
| callback(*this, form, field_id, timestamp); |
| } |
| } |
| |
| void AutofillDriverIOS::SetParent(base::WeakPtr<AutofillDriverIOS> parent) { |
| if (unregistered_) { |
| // Do not set parent if the driver was unregistered to avoid any risk |
| // of connecting it back into a form tree, where it has to be kept alone as |
| // a standalone node. |
| return; |
| } |
| |
| parent_ = std::move(parent); |
| } |
| |
| void AutofillDriverIOS::SetSelfAsParent(const autofill::FormData& form, |
| LocalFrameToken token) { |
| AutofillDriverIOS* child_driver = |
| FromWebStateAndLocalFrameToken(web_state_, token); |
| if (child_driver) { |
| child_driver->SetParent(weak_ptr_factory_.GetWeakPtr()); |
| } |
| // Redeclare the forms as seen to take into account the new parent to |
| // establish the relation between the child frames and their host form in the |
| // forms tree. |
| auto callback = [](AutofillDriver& driver, |
| const std::vector<FormData>& updated_forms, |
| const std::vector<FormGlobalId>& removed_forms) { |
| driver.GetAutofillManager().OnFormsSeen(updated_forms, removed_forms); |
| }; |
| router_->FormsSeen(callback, *this, {form}, {}); |
| } |
| |
| void AutofillDriverIOS::UpdateLastInteractedForm( |
| const FormData& form_data, |
| const FieldRendererId& formless_field) { |
| last_interacted_form_.emplace(form_data, formless_field); |
| } |
| |
| void AutofillDriverIOS::ClearLastInteractedForm() { |
| last_interacted_form_.reset(); |
| } |
| |
| void AutofillDriverIOS::OnAutofillManagerStateChanged( |
| AutofillManager& manager, |
| LifecycleState old_state, |
| LifecycleState new_state) { |
| switch (new_state) { |
| case LifecycleState::kInactive: |
| case LifecycleState::kActive: |
| case LifecycleState::kPendingReset: |
| break; |
| case LifecycleState::kPendingDeletion: |
| manager_observation_.Reset(); |
| break; |
| } |
| } |
| |
| void AutofillDriverIOS::OnAfterFormsSeen( |
| AutofillManager& manager, |
| base::span<const FormGlobalId> updated_forms, |
| base::span<const FormGlobalId> removed_forms) { |
| DCHECK_EQ(&manager, manager_.get()); |
| if (updated_forms.empty()) { |
| return; |
| } |
| std::vector<raw_ptr<FormStructure, VectorExperimental>> form_structures; |
| form_structures.reserve(updated_forms.size()); |
| for (const FormGlobalId& form : updated_forms) { |
| if (FormStructure* form_structure = manager.FindCachedFormById(form)) { |
| form_structures.push_back(form_structure); |
| } |
| } |
| if (web::WebFrame* frame = web_frame()) { |
| [bridge_ handleParsedForms:form_structures inFrame:frame]; |
| } |
| } |
| |
| void AutofillDriverIOS::FormsRemoved( |
| const std::set<FormRendererId>& removed_forms, |
| const std::set<FieldRendererId>& removed_unowned_fields) { |
| bool submission_detected = DetectFormSubmissionAfterFormRemoval( |
| removed_forms, removed_unowned_fields); |
| RecordFormRemoval( |
| submission_detected, /*removed_forms_count=*/removed_forms.size(), |
| /*removed_unowned_fields_count=*/removed_unowned_fields.size()); |
| |
| if (submission_detected) { |
| UpdateLastInteractedFormFromFieldDataManager(); |
| |
| FormSubmitted(last_interacted_form_->form_data, |
| mojom::SubmissionSource::XHR_SUCCEEDED); |
| } |
| |
| // Report the removed forms so Autofill can be synced with the DOM state. |
| |
| std::vector<FormGlobalId> forms_to_report = |
| base::ToVector(removed_forms, [&](const auto& renderer_id) { |
| return FormGlobalId{.frame_token = local_frame_token_, |
| .renderer_id = renderer_id}; |
| }); |
| |
| if (!removed_unowned_fields.empty()) { |
| // Determine whether the synthetic form should be reported as removed based |
| // on the removed fields, where having all fields removed is considered as |
| // a deletion. |
| FormGlobalId synthetic_global_id = {.frame_token = local_frame_token_, |
| .renderer_id = FormRendererId(0)}; |
| if (FormStructure* form = |
| GetAutofillManager().FindCachedFormById(synthetic_global_id)) { |
| std::set<FieldRendererId> form_fields; |
| std::ranges::transform(form->fields(), |
| std::inserter(form_fields, form_fields.begin()), |
| [](const std::unique_ptr<AutofillField>& field) { |
| return field->renderer_id(); |
| }); |
| // If the synthetic form fields are a subset of the removed fields, it |
| // means that all the synthetic form fields were removed. |
| const bool is_deleted = |
| std::ranges::includes(removed_unowned_fields, form_fields); |
| if (is_deleted) { |
| forms_to_report.emplace_back(synthetic_global_id); |
| } |
| } |
| |
| // TODO(crbug.com/351685487): Trigger forms extraction if there are some |
| // fields removed but not all of them. |
| } |
| |
| if (!forms_to_report.empty()) { |
| FormsSeen(/*updated_forms=*/{}, /*removed_forms=*/forms_to_report); |
| } |
| } |
| |
| bool AutofillDriverIOS::DetectFormSubmissionAfterFormRemoval( |
| const std::set<FormRendererId>& removed_forms, |
| const std::set<FieldRendererId>& removed_unowned_fields) const { |
| // Detect a form submission only if the last interacted form or formless field |
| // was removed. |
| if (!last_interacted_form_) { |
| return false; |
| } |
| |
| const auto& last_interacted_form_id = |
| last_interacted_form_->form_data.renderer_id(); |
| // Check if the last interacted form was removed. |
| if (last_interacted_form_id && |
| removed_forms.find(last_interacted_form_id) != removed_forms.end()) { |
| return true; |
| } |
| |
| const auto& last_formless_field_id = last_interacted_form_->formless_field; |
| |
| // Check if the last interacted formless field was removed. |
| return removed_unowned_fields.find(last_formless_field_id) != |
| removed_unowned_fields.end(); |
| } |
| |
| void AutofillDriverIOS::Unregister() { |
| router_->UnregisterDriver(*this, /*driver_is_dying=*/true); |
| unregistered_ = true; |
| } |
| |
| void AutofillDriverIOS::OnDidTriggerFormFetch() { |
| ++form_extraction_trigger_count_; |
| } |
| |
| void AutofillDriverIOS::UpdateLastInteractedFormFromFieldDataManager() { |
| CHECK(last_interacted_form_); |
| |
| auto* frame = web_frame(); |
| if (!frame) { |
| return; |
| } |
| |
| FieldDataManager* field_data_manager = |
| FieldDataManagerFactoryIOS::FromWebFrame(frame); |
| |
| // Update the snapshot of the last interacted form with the data in |
| // FieldDataManager. |
| std::vector<FormFieldData> fields = |
| last_interacted_form_->form_data.ExtractFields(); |
| for (auto& field : fields) { |
| const auto& field_id = field.renderer_id(); |
| if (!field_data_manager->HasFieldData(field_id)) { |
| continue; |
| } |
| field.set_value(field_data_manager->GetUserInput(field_id)); |
| field.set_properties_mask( |
| field_data_manager->GetFieldPropertiesMask(field_id)); |
| } |
| last_interacted_form_->form_data.set_fields(std::move(fields)); |
| } |
| |
| void AutofillDriverIOS::RecordFormRemoval(bool submission_detected, |
| int removed_forms_count, |
| int removed_unowned_fields_count) { |
| base::UmaHistogramBoolean(/*name=*/kFormSubmissionAfterFormRemovalHistogram, |
| /*sample=*/submission_detected); |
| base::UmaHistogramCounts100( |
| /*name=*/kFormRemovalRemovedUnownedFieldsHistogram, |
| /*sample=*/removed_unowned_fields_count); |
| |
| } |
| |
| void AutofillDriverIOS::RecordTriggeredFormExtractionMetrics() { |
| if (form_extraction_trigger_count_ < 1) { |
| // Do not record anything if no extraction was performed to not pollute |
| // the data with 0 extraction cases that don't really mean anything. |
| // We usually expect at least one extraction to be triggered in the frame |
| // when its content is loaded. We are not interested in tracking the cases |
| // where extraction didn't happen, not for these metrics at least. |
| return; |
| } |
| |
| base::UmaHistogramCounts100( |
| "Autofill.iOS.TriggeredFormExtractionFromDriver.SmallRange", |
| form_extraction_trigger_count_); |
| base::UmaHistogramCounts1000( |
| "Autofill.iOS.TriggeredFormExtractionFromDriver.MediumRange", |
| form_extraction_trigger_count_); |
| base::UmaHistogramCounts10000( |
| "Autofill.iOS.TriggeredFormExtractionFromDriver.LargeRange", |
| form_extraction_trigger_count_); |
| } |
| |
| void AutofillDriverIOS::DispatchEmailVerifiedEvent( |
| FieldGlobalId field_id, |
| const std::string& presentation_token) { |
| // TODO(crbug.com/380367784): Implement email verification on iOS. |
| NOTIMPLEMENTED(); |
| } |
| |
| } // namespace autofill |