chromium/components/sensitive_content/sensitive_content_manager.cc

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "components/sensitive_content/sensitive_content_manager.h"

#include "base/check_deref.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/sensitive_content/sensitive_content_client.h"
#include "content/public/browser/web_contents.h"

namespace sensitive_content {

namespace {

using LifecycleState = autofill::AutofillDriver::LifecycleState;
using autofill::AutofillField;
using autofill::AutofillManager;
using autofill::FieldType;
using autofill::FieldTypeGroup;
using autofill::FormGlobalId;

bool IsSensitiveAutofillType(FieldType type) {
  static constexpr autofill::FieldTypeSet kNonSensitivePasswordTypes = {
      FieldType::NOT_ACCOUNT_CREATION_PASSWORD, FieldType::NOT_NEW_PASSWORD,
      FieldType::NOT_PASSWORD, FieldType::NOT_USERNAME};
  FieldTypeGroup field_type_group = autofill::GroupTypeOfFieldType(type);
  // Return true if the field is a credit card or password form field.
  // A field is a password form field if it's part of
  // `FieldTypeGroup::kPasswordField`, but it's not prefixed with "NOT_".
  return field_type_group == FieldTypeGroup::kCreditCard ||
         field_type_group == FieldTypeGroup::kStandaloneCvcField ||
         (field_type_group == FieldTypeGroup::kPasswordField &&
          !kNonSensitivePasswordTypes.contains(type));
}

}  // namespace

SensitiveContentManager::SensitiveContentManager(
    content::WebContents* web_contents,
    SensitiveContentClient* client)
    : client_(CHECK_DEREF(client)) {
  autofill_managers_observation_.Observe(web_contents);
}

SensitiveContentManager::~SensitiveContentManager() = default;

void SensitiveContentManager::UpdateContentSensitivity() {
  const bool content_is_sensitive = !sensitive_fields_.empty();
  // Prevent unnecessary calls to the client.
  if (last_content_was_sensitive_ != content_is_sensitive) {
    client_->SetContentSensitivity(!sensitive_fields_.empty());
    last_content_was_sensitive_ = content_is_sensitive;
  }
}

void SensitiveContentManager::OnFieldTypesDetermined(AutofillManager& manager,
                                                     FormGlobalId form_id,
                                                     FieldTypeSource) {
  if (const autofill::FormStructure* form =
          manager.FindCachedFormById(form_id)) {
    for (const std::unique_ptr<AutofillField>& field : form->fields()) {
      if (IsSensitiveAutofillType(field->Type().GetStorableType())) {
        sensitive_fields_.insert(field->global_id());
      } else {
        sensitive_fields_.erase(field->global_id());
      }
    }
    UpdateContentSensitivity();
  }
}

void SensitiveContentManager::OnBeforeFormsSeen(
    AutofillManager& manager,
    base::span<const autofill::FormGlobalId> updated_forms,
    base::span<const autofill::FormGlobalId> removed_forms) {
  for (const FormGlobalId& form_id : removed_forms) {
    if (const autofill::FormStructure* form =
            manager.FindCachedFormById(form_id)) {
      for (const std::unique_ptr<AutofillField>& field : form->fields()) {
        sensitive_fields_.erase(field->global_id());
      }
    }
  }
  UpdateContentSensitivity();
}

void SensitiveContentManager::OnAutofillManagerStateChanged(
    autofill::AutofillManager& manager,
    LifecycleState previous,
    LifecycleState current) {
  autofill::LocalFrameToken local_frame_token =
      manager.driver().GetFrameToken();

  if (previous == LifecycleState::kActive &&
      current != LifecycleState::kActive) {
    // The frame is not active anymore, so its fields are not anymore in the
    // DOM.
    // If needed, the time complexity can be further improved here by exploiting
    // that fields from the same frame are next to each other in the set.
    std::erase_if(sensitive_fields_,
                  [local_frame_token](autofill::FieldGlobalId field_id) {
                    return field_id.frame_token == local_frame_token;
                  });
  } else if (previous != LifecycleState::kActive &&
             current == LifecycleState::kActive) {
    // The frame became active, so its fields are present in the DOM again.
    const std::map<FormGlobalId, std::unique_ptr<autofill::FormStructure>>&
        forms = manager.form_structures();

    for (const auto& [form_id, form_structure] : forms) {
      const std::vector<std::unique_ptr<AutofillField>>& fields =
          form_structure->fields();
      for (const std::unique_ptr<AutofillField>& field : fields) {
        if (IsSensitiveAutofillType(field->Type().GetStorableType())) {
          sensitive_fields_.insert(field->global_id());
        }
      }
    }
  }

  UpdateContentSensitivity();
}

}  // namespace sensitive_content