chromium/components/autofill/ios/browser/autofill_driver_ios.mm

// 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 "base/check_deref.h"
#import "base/containers/contains.h"
#import "base/containers/to_vector.h"
#import "base/memory/ptr_util.h"
#import "base/memory/raw_ptr.h"
#import "base/metrics/histogram_functions.h"
#import "base/observer_list.h"
#import "components/autofill/core/browser/autofill_driver_router.h"
#import "components/autofill/core/browser/form_filler.h"
#import "components/autofill/core/browser/form_structure.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_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/common/field_data_manager_factory_ios.h"
#import "components/autofill/ios/form_util/child_frame_registrar.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 "ui/accessibility/ax_tree_id.h"
#import "ui/gfx/geometry/rect_f.h"
#import "url/origin.h"

namespace autofill {

namespace {
// 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);
}
}  // namespace

// static
AutofillDriverIOS* AutofillDriverIOS::FromWebStateAndWebFrame(
    web::WebState* web_state,
    web::WebFrame* web_frame) {
  return AutofillDriverIOSFactory::FromWebState(web_state)->DriverForFrame(
      web_frame);
}

// 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,
    const std::string& app_locale,
    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, app_locale)),
      router_(router) {
  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();
}

LocalFrameToken AutofillDriverIOS::GetFrameToken() const {
  return local_frame_token_;
}

std::optional<LocalFrameToken> AutofillDriverIOS::Resolve(FrameToken query) {
  if (!IsAcrossIframesEnabled()) {
    return std::nullopt;
  }

  if (absl::holds_alternative<LocalFrameToken>(query)) {
    return absl::get<LocalFrameToken>(query);
  }
  CHECK(absl::holds_alternative<RemoteFrameToken>(query));
  auto remote_token = absl::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_;
}

// Return true as iOS has no MPArch.
bool AutofillDriverIOS::IsActive() const {
  return true;
}

bool AutofillDriverIOS::IsInAnyMainFrame() const {
  web::WebFrame* frame = web_frame();
  return frame ? frame->IsMainFrame() : true;
}

bool AutofillDriverIOS::HasSharedAutofillPermission() const {
  // Give the shared-autofill permission to the main frame of the webstate by
  // default.
  if (IsInAnyMainFrame()) {
    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_->IsInAnyMainFrame() &&
      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) {
  switch (action_type) {
    case mojom::FormActionType::kUndo:
      // TODO(crbug.com/40266549) Add Undo support on iOS.
      return {};
    case mojom::FormActionType::kFill: {
      auto callback = [](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 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 {
        std::vector<FieldGlobalId> safe_fields;
        for (const auto& field : fields) {
          safe_fields.push_back(field.global_id());
        }

        callback(
            *this, action_type, action_persistence,
            std::vector<FormFieldData::FillData>(fields.begin(), fields.end()));
        return safe_fields;
      }
    }
  }
}

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,
    base::OnceCallback<void(AutofillDriver*, const std::optional<FormData>&)>
        response_callback) {
  // TODO(crbug.com/40284824): Implement ExtractForm().
  NOTIMPLEMENTED();
}

void AutofillDriverIOS::SendTypePredictionsToRenderer(
    const std::vector<raw_ptr<FormStructure, VectorExperimental>>& forms) {
  std::vector<FormDataPredictions> preds =
      FormStructure::GetFieldTypePredictions(forms);

  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, preds);
  } else {
    callback(*this, preds);
  }
}

void AutofillDriverIOS::RendererShouldAcceptDataListSuggestion(
    const FieldGlobalId& field_id,
    const std::u16string& value) {}

void AutofillDriverIOS::TriggerFormExtractionInDriverFrame(
    AutofillDriverRouterAndFormForestPassKey pass_key) {
  if (!is_processed()) {
    return;
  }
  [bridge_ scanFormsInWebState:web_state_ inFrame:web_frame()];
}

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::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) {
    driver.GetAutofillManager().OnAskForValuesToFill(
        form, field_id, bounding_box, trigger_source);
  };
  // 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);
  } else {
    callback(*this, form, field_id, caret_bounds,
             autofill::AutofillSuggestionTriggerSource::kiOS);
  }
}

void AutofillDriverIOS::DidFillAutofillFormData(const FormData& form,
                                                base::TimeTicks timestamp) {
  auto callback = [](AutofillDriver& driver, const FormData& form,
                     base::TimeTicks timestamp) {
    cast(&driver)->UpdateLastInteractedForm(/*form_data=*/form);
    driver.GetAutofillManager().OnDidFillAutofillFormData(form, timestamp);
  };
  if (IsAcrossIframesEnabled()) {
    router_->DidFillAutofillFormData(callback, *this, form, timestamp);
  } else {
    callback(*this, form, timestamp);
  }
}

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 absl::get is safe because on iOS, FormData::child_frames is
        // only ever populated with RemoteFrameTokens. absl::get will fail a
        // CHECK if this assumption is ever wrong.
        auto token = absl::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,
    bool known_success,
    mojom::SubmissionSource submission_source) {
  auto callback = [](AutofillDriver& driver, const FormData& form,
                     bool known_success,
                     mojom::SubmissionSource submission_source) {
    base::UmaHistogramEnumeration(kAutofillSubmissionDetectionSourceHistogram,
                                  submission_source);
    driver.GetAutofillManager().OnFormSubmitted(form, known_success,
                                                submission_source);
    cast(&driver)->ClearLastInteractedForm();
  };
  if (IsAcrossIframesEnabled()) {
    router_->FormSubmitted(callback, *this, form, known_success,
                           submission_source);
  } else {
    callback(*this, form, known_success, submission_source);
  }
}

void AutofillDriverIOS::CaretMovedInFormField(const FormData& form,
                                              const FieldGlobalId& field_id,
                                              const gfx::Rect& caret_bounds) {
  GetAutofillManager().OnCaretMovedInFormField(form, field_id, caret_bounds);
}

void AutofillDriverIOS::TextFieldDidChange(const FormData& form,
                                           const FieldGlobalId& field_id,
                                           base::TimeTicks timestamp) {
  auto callback = [&](AutofillDriver& driver, const FormData& form,
                      const FieldGlobalId& field_global_id,
                      base::TimeTicks timestamp) {
    cast(&driver)->UpdateLastInteractedForm(
        /*form_data=*/form,
        /*formless_field=*/form.renderer_id() ? FieldRendererId()
                                              : field_global_id.renderer_id);
    driver.GetAutofillManager().OnTextFieldDidChange(form, field_id, timestamp);
  };

  if (IsAcrossIframesEnabled()) {
    router_->TextFieldDidChange(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) {
  const 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,
                  /*known_success=*/true,
                  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;
      base::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 =
          base::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::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=*/kFormRemovalRemovedFormsHistogram,
                              /*sample=*/removed_forms_count);
  base::UmaHistogramCounts100(
      /*name=*/kFormRemovalRemovedUnownedFieldsHistogram,
      /*sample=*/removed_unowned_fields_count);

  if (submission_detected) {
    CHECK(last_interacted_form_);
    base::UmaHistogramBoolean(
        /*name=*/kFormlessSubmissionAfterFormRemovalHistogram,
        /*sample=*/!last_interacted_form_->form_data.renderer_id());
  }
}

}  // namespace autofill