| // 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/plus_addresses/plus_address_service_impl.h" |
| |
| #include <memory> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/check_deref.h" |
| #include "base/check_op.h" |
| #include "base/containers/to_vector.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/metrics/user_metrics.h" |
| #include "base/metrics/user_metrics_action.h" |
| #include "base/notimplemented.h" |
| #include "base/notreached.h" |
| #include "base/scoped_observation.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "components/affiliations/core/browser/affiliation_utils.h" |
| #include "components/autofill/core/browser/data_quality/validation.h" |
| #include "components/autofill/core/browser/ui/suggestion.h" |
| #include "components/autofill/core/browser/ui/suggestion_hiding_reason.h" |
| #include "components/autofill/core/browser/ui/suggestion_type.h" |
| #include "components/autofill/core/common/aliases.h" |
| #include "components/autofill/core/common/form_field_data.h" |
| #include "components/autofill/core/common/plus_address_survey_type.h" |
| #include "components/plus_addresses/features.h" |
| #include "components/plus_addresses/metrics/plus_address_metrics.h" |
| #include "components/plus_addresses/plus_address_allocator.h" |
| #include "components/plus_addresses/plus_address_blocklist_data.h" |
| #include "components/plus_addresses/plus_address_hats_utils.h" |
| #include "components/plus_addresses/plus_address_http_client.h" |
| #include "components/plus_addresses/plus_address_http_client_impl.h" |
| #include "components/plus_addresses/plus_address_jit_allocator.h" |
| #include "components/plus_addresses/plus_address_preallocator.h" |
| #include "components/plus_addresses/plus_address_prefs.h" |
| #include "components/plus_addresses/plus_address_suggestion_generator.h" |
| #include "components/plus_addresses/plus_address_types.h" |
| #include "components/plus_addresses/plus_address_ui_utils.h" |
| #include "components/plus_addresses/settings/plus_address_setting_service.h" |
| #include "components/plus_addresses/webdata/plus_address_sync_util.h" |
| #include "components/plus_addresses/webdata/plus_address_webdata_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/public/base/consent_level.h" |
| #include "components/signin/public/identity_manager/account_info.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/webdata/common/web_data_results.h" |
| #include "google_apis/gaia/google_service_auth_error.h" |
| #include "net/http/http_status_code.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "url/origin.h" |
| |
| namespace plus_addresses { |
| |
| namespace { |
| |
| using autofill::AutofillSuggestionTriggerSource; |
| using autofill::FormFieldData; |
| using autofill::Suggestion; |
| using autofill::SuggestionType; |
| using PasswordFormClassification = autofill::PasswordFormClassification; |
| |
| constexpr char16_t kPlusAddressDomain[] = u"@grelay.com"; |
| |
| affiliations::FacetURI OriginToFacet(const url::Origin& origin) { |
| // For a valid `origin`, `origin.GetURL().spec()` is always a valid spec. |
| // However, using `FacetURI::FromCanonicalSpec(spec)` can lead to mismatches |
| // in the underlying representation, since it uses the spec verbatim. E.g., |
| // a trailing "/" is removed by `FacetURI::FromPotentiallyInvalidSpec()`, |
| // but kept by `FacetURI::FromCanonicalSpec(spec)`. |
| // TODO(b/338342346): Revise `FacetURI::FromCanonicalSpec()`. |
| return affiliations::FacetURI::FromPotentiallyInvalidSpec( |
| origin.GetURL().spec()); |
| } |
| |
| std::unique_ptr<PlusAddressAllocator> CreateAllocator( |
| PrefService* pref_service, |
| PlusAddressSettingService* setting_service, |
| PlusAddressHttpClient* http_client, |
| PlusAddressPreallocator::IsEnabledCheck is_enabled_check) { |
| if (base::FeatureList::IsEnabled(features::kPlusAddressPreallocation)) { |
| return std::make_unique<PlusAddressPreallocator>( |
| pref_service, setting_service, http_client, |
| std::move(is_enabled_check)); |
| } |
| return std::make_unique<PlusAddressJitAllocator>(http_client); |
| } |
| |
| // Returns `true` if the origin is part of the set of blocklisted domains and |
| // `false` otherwise. This means that the domain's origin matches the |
| // `exclusion_pattern` regex and does not match the `exception_pattern` regex. |
| bool IsSiteExcluded(const url::Origin& origin) { |
| const PlusAddressBlocklistData& blocklist_data = |
| PlusAddressBlocklistData::GetInstance(); |
| |
| const re2::RE2* exception_pattern = blocklist_data.GetExceptionPattern(); |
| if (exception_pattern && |
| RE2::PartialMatch(origin.host(), *exception_pattern)) { |
| return false; |
| } |
| |
| const re2::RE2* exclusion_pattern = blocklist_data.GetExclusionPattern(); |
| return exclusion_pattern && |
| RE2::PartialMatch(origin.host(), *exclusion_pattern); |
| } |
| |
| std::string GetPlusAddressFromPlusProfile( |
| const PlusProfile& affiliated_profile) { |
| return affiliated_profile.plus_address.value(); |
| } |
| |
| } // namespace |
| |
| PlusAddressServiceImpl::PlusAddressServiceImpl( |
| PrefService* pref_service, |
| signin::IdentityManager* identity_manager, |
| PlusAddressSettingService* setting_service, |
| std::unique_ptr<PlusAddressHttpClient> plus_address_http_client, |
| scoped_refptr<PlusAddressWebDataService> webdata_service, |
| affiliations::AffiliationService* affiliation_service, |
| FeatureEnabledForProfileCheck feature_enabled_for_profile_check) |
| : pref_service_(CHECK_DEREF(pref_service)), |
| identity_manager_(CHECK_DEREF(identity_manager)), |
| setting_service_(CHECK_DEREF(setting_service)), |
| submission_logger_( |
| identity_manager, |
| base::BindRepeating(&PlusAddressServiceImpl::IsPlusAddress, |
| base::Unretained(this))), |
| plus_address_http_client_(std::move(plus_address_http_client)), |
| webdata_service_(std::move(webdata_service)), |
| plus_address_match_helper_(this, affiliation_service), |
| feature_enabled_for_profile_check_( |
| std::move(feature_enabled_for_profile_check)) { |
| // The allocator is created in the body of the constructor to avoid that it |
| // calls into `this` before all members are assigned. |
| plus_address_allocator_ = |
| CreateAllocator(&pref_service_.get(), &setting_service_.get(), |
| plus_address_http_client_.get(), |
| base::BindRepeating(&PlusAddressServiceImpl::IsEnabled, |
| base::Unretained(this))); |
| |
| if (webdata_service_) { |
| webdata_service_observation_.Observe(webdata_service_.get()); |
| if (IsEnabled()) { |
| webdata_service_->GetPlusProfiles(this); |
| } |
| } |
| identity_manager_observation_.Observe(identity_manager); |
| } |
| |
| PlusAddressServiceImpl::~PlusAddressServiceImpl() { |
| for (PlusAddressService::Observer& o : observers_) { |
| o.OnPlusAddressServiceShutdown(); |
| } |
| } |
| |
| void PlusAddressServiceImpl::AddObserver(PlusAddressService::Observer* o) { |
| observers_.AddObserver(o); |
| } |
| |
| void PlusAddressServiceImpl::RemoveObserver(PlusAddressService::Observer* o) { |
| observers_.RemoveObserver(o); |
| } |
| |
| bool PlusAddressServiceImpl::IsPlusAddressCreationEnabled( |
| const url::Origin& origin, |
| bool is_off_the_record) const { |
| // Disabled plus address filling implies that plus address creation is |
| // disabled. |
| if (!IsPlusAddressFillingEnabled(origin)) { |
| return false; |
| } |
| |
| // Only offer plus address creation on https domains. |
| if (origin.scheme() != url::kHttpsScheme) { |
| return false; |
| } |
| |
| // Don't offer plus address creation for off-the-record sessions. |
| if (is_off_the_record) { |
| return false; |
| } |
| |
| // We've met the prerequisites. If this isn't an OTR session and the global |
| // settings toggle isn't off, plus address creation is supported. |
| return !base::FeatureList::IsEnabled(features::kPlusAddressGlobalToggle) || |
| setting_service_->GetIsPlusAddressesEnabled(); |
| } |
| |
| bool PlusAddressServiceImpl::ShouldShowManualFallback( |
| const url::Origin& origin, |
| bool is_off_the_record) const { |
| if (!IsPlusAddressFillingEnabled(origin)) { |
| return false; |
| } |
| |
| // If there's an existing plus_address with a facet equal to `origin` (i.e. no |
| // affiliations considered), it's supported. |
| if (GetPlusProfile(OriginToFacet(origin)).has_value()) { |
| return true; |
| } |
| |
| // Unless there's an existing plus address for `origin`, off-the-record |
| // sessions are not supported. |
| if (is_off_the_record) { |
| return false; |
| } |
| |
| // If the user doesn't have an existing plus address for `origin` and this |
| // session is not off-the-record, the global toggle must be enabled. |
| return !base::FeatureList::IsEnabled(features::kPlusAddressGlobalToggle) || |
| setting_service_->GetIsPlusAddressesEnabled(); |
| } |
| |
| std::optional<PlusAddress> PlusAddressServiceImpl::GetPlusAddress( |
| const affiliations::FacetURI& facet) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::optional<PlusProfile> profile = GetPlusProfile(facet); |
| return profile ? std::make_optional(std::move(profile->plus_address)) |
| : std::nullopt; |
| } |
| |
| void PlusAddressServiceImpl::GetAffiliatedPlusProfiles( |
| const url::Origin& origin, |
| GetPlusProfilesCallback callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| plus_address_match_helper_.GetAffiliatedPlusProfiles(OriginToFacet(origin), |
| std::move(callback)); |
| } |
| |
| base::span<const PlusProfile> PlusAddressServiceImpl::GetPlusProfiles() const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return plus_address_cache_.GetPlusProfiles(); |
| } |
| |
| std::optional<PlusProfile> PlusAddressServiceImpl::GetPlusProfile( |
| const affiliations::FacetURI& facet) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!facet.is_valid()) { |
| return std::nullopt; |
| } |
| return plus_address_cache_.FindByFacet(facet); |
| } |
| |
| void PlusAddressServiceImpl::SavePlusProfile(const PlusProfile& profile) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK(profile.is_confirmed); |
| // New plus addresses are requested directly from the PlusAddress backend. |
| // These addresses become later available through sync. Until the address |
| // shows up in sync, it should still be available through |
| // `PlusAddressServiceImpl`, even after reloading the data. This requires |
| // adding the address to the database. |
| if (webdata_service_) { |
| webdata_service_->AddOrUpdatePlusProfile(profile); |
| } |
| // Update the in-memory plus profiles cache. |
| plus_address_cache_.InsertProfile(profile); |
| for (PlusAddressService::Observer& o : observers_) { |
| o.OnPlusAddressesChanged( |
| {PlusAddressDataChange(PlusAddressDataChange::Type::kAdd, profile)}); |
| } |
| } |
| |
| bool PlusAddressServiceImpl::IsPlusAddress( |
| const std::string& potential_plus_address) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return plus_address_cache_.IsPlusAddress(potential_plus_address); |
| } |
| |
| bool PlusAddressServiceImpl::MatchesPlusAddressFormat( |
| const std::u16string& value) const { |
| return autofill::IsValidEmailAddress(value) && |
| value.ends_with(kPlusAddressDomain); |
| } |
| |
| bool PlusAddressServiceImpl::IsPlusAddressFillingEnabled( |
| const url::Origin& origin) const { |
| // Check that the feature is enabled and the origin is supported (not opaque, |
| // excluded, or is non http/https scheme) |
| return IsEnabled() && IsSupportedOrigin(origin); |
| } |
| |
| bool PlusAddressServiceImpl::IsPlusAddressFullFormFillingEnabled() const { |
| return base::FeatureList::IsEnabled(features::kPlusAddressFullFormFill); |
| } |
| |
| void PlusAddressServiceImpl::GetAffiliatedPlusAddresses( |
| const url::Origin& origin, |
| base::OnceCallback<void(std::vector<std::string>)> callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| plus_address_match_helper_.GetAffiliatedPlusProfiles( |
| OriginToFacet(origin), |
| base::BindOnce( |
| [](base::OnceCallback<void(std::vector<std::string>)> inner_callback, |
| std::vector<PlusProfile> affiliated_profiles) { |
| std::vector<std::string> plus_addresses = base::ToVector( |
| affiliated_profiles, GetPlusAddressFromPlusProfile); |
| std::move(inner_callback).Run(std::move(plus_addresses)); |
| }, |
| std::move(callback))); |
| } |
| |
| std::vector<Suggestion> PlusAddressServiceImpl::GetSuggestionsFromPlusAddresses( |
| const std::vector<std::string>& plus_addresses, |
| const url::Origin& origin, |
| bool is_off_the_record, |
| const autofill::FormData& focused_form, |
| const base::flat_map<autofill::FieldGlobalId, autofill::FieldTypeGroup>& |
| form_field_type_groups, |
| const autofill::PasswordFormClassification& focused_form_classification, |
| const autofill::FieldGlobalId& focused_field_id, |
| AutofillSuggestionTriggerSource trigger_source) { |
| if (!IsPlusAddressFillingEnabled(origin)) { |
| return {}; |
| } |
| |
| const bool is_creation_enabled = |
| IsPlusAddressCreationEnabled(origin, is_off_the_record); |
| std::vector<Suggestion> suggestions = |
| PlusAddressSuggestionGenerator(&setting_service_.get(), |
| plus_address_allocator_.get(), |
| std::move(origin)) |
| .GetSuggestions(plus_addresses, is_creation_enabled, focused_form, |
| form_field_type_groups, focused_form_classification, |
| focused_field_id, trigger_source); |
| const autofill::DenseSet<SuggestionType> suggestion_types(suggestions, |
| &Suggestion::type); |
| |
| using enum AutofillPlusAddressDelegate::SuggestionEvent; |
| if (suggestion_types.contains(SuggestionType::kFillExistingPlusAddress)) { |
| RecordAutofillSuggestionEvent(kExistingPlusAddressSuggested); |
| } else if (suggestion_types.contains_any( |
| {SuggestionType::kCreateNewPlusAddress, |
| SuggestionType::kCreateNewPlusAddressInline})) { |
| RecordAutofillSuggestionEvent(kCreateNewPlusAddressSuggested); |
| } |
| return suggestions; |
| } |
| |
| Suggestion PlusAddressServiceImpl::GetManagePlusAddressSuggestion() const { |
| return PlusAddressSuggestionGenerator::GetManagePlusAddressSuggestion(); |
| } |
| |
| void PlusAddressServiceImpl::ReservePlusAddress( |
| const url::Origin& origin, |
| PlusAddressRequestCallback on_completed) { |
| if (!IsEnabled()) { |
| std::move(on_completed) |
| .Run(base::unexpected(PlusAddressRequestError( |
| PlusAddressRequestErrorType::kUserSignedOut))); |
| return; |
| } |
| plus_address_allocator_->AllocatePlusAddress( |
| origin, PlusAddressAllocator::AllocationMode::kAny, |
| base::BindOnce(&PlusAddressServiceImpl::HandleCreateOrConfirmResponse, |
| base::Unretained(this)) |
| .Then(std::move(on_completed))); |
| } |
| |
| void PlusAddressServiceImpl::RefreshPlusAddress( |
| const url::Origin& origin, |
| PlusAddressRequestCallback on_completed) { |
| if (!IsEnabled()) { |
| std::move(on_completed) |
| .Run(base::unexpected(PlusAddressRequestError( |
| PlusAddressRequestErrorType::kUserSignedOut))); |
| return; |
| } |
| plus_address_allocator_->AllocatePlusAddress( |
| origin, PlusAddressAllocator::AllocationMode::kNewPlusAddress, |
| base::BindOnce(&PlusAddressServiceImpl::HandleCreateOrConfirmResponse, |
| base::Unretained(this)) |
| .Then(std::move(on_completed))); |
| } |
| |
| bool PlusAddressServiceImpl::IsRefreshingSupported(const url::Origin& origin) { |
| return plus_address_allocator_->IsRefreshingSupported(origin); |
| } |
| |
| void PlusAddressServiceImpl::ConfirmPlusAddress( |
| const url::Origin& origin, |
| const PlusAddress& plus_address, |
| PlusAddressRequestCallback on_completed) { |
| if (!IsEnabled()) { |
| std::move(on_completed) |
| .Run(base::unexpected(PlusAddressRequestError( |
| PlusAddressRequestErrorType::kUserSignedOut))); |
| return; |
| } |
| // Check the local mapping before attempting to confirm plus_address. |
| if (std::optional<PlusProfile> stored_plus_profile = |
| GetPlusProfile(OriginToFacet(origin)); |
| stored_plus_profile) { |
| std::move(on_completed).Run(stored_plus_profile.value()); |
| return; |
| } |
| |
| // We remove the allocated plus address here even though the creation call |
| // may not go through. UI code may offer the user to re-attempt to confirm |
| // a plus address, e.g. in the case of time out. |
| plus_address_allocator_->RemoveAllocatedPlusAddress(plus_address); |
| plus_address_http_client_->ConfirmPlusAddress( |
| origin, plus_address, |
| base::BindOnce(&PlusAddressServiceImpl::HandleCreateOrConfirmResponse, |
| base::Unretained(this)) |
| .Then(std::move(on_completed))); |
| } |
| |
| const PlusProfileOrError& PlusAddressServiceImpl::HandleCreateOrConfirmResponse( |
| const PlusProfileOrError& maybe_profile) { |
| if (maybe_profile.has_value() && maybe_profile->is_confirmed) { |
| SavePlusProfile(*maybe_profile); |
| } |
| return maybe_profile; |
| } |
| |
| std::optional<std::string> PlusAddressServiceImpl::GetPrimaryEmail() { |
| if (!identity_manager_->HasPrimaryAccount(signin::ConsentLevel::kSignin)) { |
| return std::nullopt; |
| } |
| // TODO(crbug.com/40276862): This is fine for prototyping, but eventually we |
| // must also take `AccountInfo::CanHaveEmailAddressDisplayed` into account |
| // here and elsewhere in this file. |
| return identity_manager_->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin) |
| .email; |
| } |
| |
| bool PlusAddressServiceImpl::IsEnabled() const { |
| if (!feature_enabled_for_profile_check_.Run( |
| features::kPlusAddressesEnabled) || |
| features::kEnterprisePlusAddressServerUrl.Get().empty()) { |
| return false; |
| } |
| |
| const auto primary_account_id = |
| identity_manager_->GetPrimaryAccountId(signin::ConsentLevel::kSignin); |
| return !primary_account_id.empty() && |
| identity_manager_ |
| ->GetErrorStateOfRefreshTokenForAccount(primary_account_id) |
| .state() == GoogleServiceAuthError::State::NONE; |
| } |
| |
| void PlusAddressServiceImpl::OnWebDataChangedBySync( |
| const std::vector<PlusAddressDataChange>& changes) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| std::vector<PlusAddressDataChange> applied_changes; |
| for (const PlusAddressDataChange& change : changes) { |
| const PlusProfile& profile = change.profile(); |
| switch (change.type()) { |
| // Sync updates affect the local cache only if they introduce changes |
| // (e.g., an added plus address that wasn't previously confirmed via |
| // ConfirmPlusAddress). |
| case PlusAddressDataChange::Type::kAdd: { |
| if (plus_address_cache_.InsertProfile(profile)) { |
| applied_changes.emplace_back(PlusAddressDataChange::Type::kAdd, |
| profile); |
| } |
| break; |
| } |
| case PlusAddressDataChange::Type::kRemove: { |
| if (plus_address_cache_.EraseProfile(profile)) { |
| applied_changes.emplace_back(PlusAddressDataChange::Type::kRemove, |
| profile); |
| } |
| break; |
| } |
| } |
| } |
| |
| for (PlusAddressService::Observer& o : observers_) { |
| o.OnPlusAddressesChanged(applied_changes); |
| } |
| } |
| |
| void PlusAddressServiceImpl::OnWebDataServiceRequestDone( |
| WebDataServiceBase::Handle handle, |
| std::unique_ptr<WDTypedResult> result) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| CHECK_EQ(result->GetType(), PLUS_ADDRESS_RESULT); |
| CHECK(plus_address_cache_.IsEmpty()); |
| |
| const std::vector<PlusProfile>& profiles = |
| static_cast<WDResult<std::vector<PlusProfile>>*>(result.get()) |
| ->GetValue(); |
| |
| std::vector<PlusAddressDataChange> applied_changes; |
| applied_changes.reserve(profiles.size()); |
| for (const PlusProfile& plus_profile : profiles) { |
| plus_address_cache_.InsertProfile(plus_profile); |
| applied_changes.emplace_back(PlusAddressDataChange::Type::kAdd, |
| plus_profile); |
| } |
| |
| for (PlusAddressService::Observer& o : observers_) { |
| o.OnPlusAddressesChanged(applied_changes); |
| } |
| } |
| |
| void PlusAddressServiceImpl::OnPrimaryAccountChanged( |
| const signin::PrimaryAccountChangeEvent& event) { |
| signin::PrimaryAccountChangeEvent::Type type = |
| event.GetEventTypeFor(signin::ConsentLevel::kSignin); |
| if (type == signin::PrimaryAccountChangeEvent::Type::kCleared) { |
| HandleSignout(); |
| } |
| } |
| |
| void PlusAddressServiceImpl::OnErrorStateOfRefreshTokenUpdatedForAccount( |
| const CoreAccountInfo& account_info, |
| const GoogleServiceAuthError& error, |
| signin_metrics::SourceForRefreshTokenOperation token_operation_source) { |
| if (auto primary_account = identity_manager_->GetPrimaryAccountInfo( |
| signin::ConsentLevel::kSignin); |
| primary_account.IsEmpty() || |
| primary_account.account_id != account_info.account_id) { |
| return; |
| } |
| if (error.state() != GoogleServiceAuthError::NONE) { |
| HandleSignout(); |
| } |
| } |
| |
| void PlusAddressServiceImpl::HandleSignout() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| plus_address_http_client_->Reset(); |
| } |
| |
| bool PlusAddressServiceImpl::IsSupportedOrigin( |
| const url::Origin& origin) const { |
| if (origin.opaque() || IsSiteExcluded(origin)) { |
| return false; |
| } |
| |
| return origin.scheme() == url::kHttpsScheme || |
| origin.scheme() == url::kHttpScheme; |
| } |
| |
| void PlusAddressServiceImpl::RecordAutofillSuggestionEvent( |
| SuggestionEvent suggestion_event) { |
| metrics::RecordAutofillSuggestionEvent(suggestion_event); |
| |
| using enum autofill::AutofillPlusAddressDelegate::SuggestionEvent; |
| switch (suggestion_event) { |
| case kRefreshPlusAddressInlineClicked: |
| base::RecordAction(base::UserMetricsAction("PlusAddresses.Refreshed")); |
| return; |
| case kExistingPlusAddressSuggested: |
| base::RecordAction(base::UserMetricsAction( |
| "PlusAddresses.StandaloneFillSuggestionShown")); |
| return; |
| case kCreateNewPlusAddressSuggested: { |
| const bool user_acepted_notice = |
| setting_service_->GetHasAcceptedNotice() || |
| !base::FeatureList::IsEnabled( |
| features::kPlusAddressUserOnboardingEnabled); |
| if (user_acepted_notice) { |
| base::RecordAction( |
| base::UserMetricsAction("PlusAddresses.CreateSuggestionShown")); |
| } else { |
| base::RecordAction(base::UserMetricsAction( |
| "PlusAddresses.CreateSuggestionFirstTimeNoticeShown")); |
| } |
| return; |
| } |
| case kCreateNewPlusAddressInlineSuggested: |
| base::RecordAction( |
| base::UserMetricsAction("PlusAddresses.CreateSuggestionShown")); |
| return; |
| case kExistingPlusAddressChosen: |
| base::RecordAction(base::UserMetricsAction( |
| "PlusAddresses.FillStandaloneSuggestionAccepted")); |
| return; |
| case kCreateNewPlusAddressChosen: |
| base::RecordAction( |
| base::UserMetricsAction("PlusAddresses.CreateSuggestionAccepted")); |
| return; |
| case kCreateNewPlusAddressInlineChosen: |
| base::RecordAction( |
| base::UserMetricsAction("PlusAddresses.OfferedPlusAddressAccepted")); |
| return; |
| case kErrorDuringReserve: |
| case kCreateNewPlusAddressInlineReserveLoadingStateShown: |
| return; |
| } |
| NOTREACHED(); |
| } |
| |
| void PlusAddressServiceImpl::OnPlusAddressSuggestionShown( |
| autofill::AutofillManager& manager, |
| autofill::FormGlobalId form, |
| autofill::FieldGlobalId field, |
| SuggestionContext suggestion_context, |
| autofill::PasswordFormClassification::Type form_type, |
| autofill::SuggestionType suggestion_type) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| submission_logger_.OnPlusAddressSuggestionShown( |
| manager, form, field, suggestion_context, form_type, suggestion_type, |
| /*plus_address_count=*/plus_address_cache_.Size()); |
| } |
| |
| void PlusAddressServiceImpl::DidFillPlusAddress() { |
| pref_service_->SetTime(prefs::kLastPlusAddressFillingTime, base::Time::Now()); |
| } |
| |
| size_t PlusAddressServiceImpl::GetPlusAddressesCount() { |
| return GetPlusProfiles().size(); |
| } |
| |
| void PlusAddressServiceImpl::OnClickedRefreshInlineSuggestion( |
| const url::Origin& last_committed_primary_main_frame_origin, |
| base::span<const autofill::Suggestion> current_suggestions, |
| size_t current_suggestion_index, |
| base::OnceCallback<void(std::vector<autofill::Suggestion>, |
| AutofillSuggestionTriggerSource)> |
| update_suggestions_callback) { |
| RecordAutofillSuggestionEvent( |
| SuggestionEvent::kRefreshPlusAddressInlineClicked); |
| std::vector<Suggestion> updated_suggestions(current_suggestions.begin(), |
| current_suggestions.end()); |
| PlusAddressSuggestionGenerator(&setting_service_.get(), |
| plus_address_allocator_.get(), |
| last_committed_primary_main_frame_origin) |
| .RefreshPlusAddressForSuggestion( |
| updated_suggestions[current_suggestion_index]); |
| std::move(update_suggestions_callback) |
| .Run( |
| std::move(updated_suggestions), |
| AutofillSuggestionTriggerSource::kPlusAddressUpdatedInBrowserProcess); |
| } |
| |
| void PlusAddressServiceImpl::OnShowedInlineSuggestion( |
| const url::Origin& primary_main_frame_origin, |
| base::span<const Suggestion> current_suggestions, |
| UpdateSuggestionsCallback update_suggestions_callback) { |
| auto it = std::ranges::find(current_suggestions, |
| SuggestionType::kCreateNewPlusAddressInline, |
| &Suggestion::type); |
| CHECK(it != current_suggestions.end()); |
| if (it->GetPayload<Suggestion::PlusAddressPayload>().address.has_value()) { |
| // Only record if this is not in a loading state - otherwise it represents |
| // a state in which we are waiting for a response from a create call. |
| if (!it->is_loading) { |
| RecordAutofillSuggestionEvent( |
| SuggestionEvent::kCreateNewPlusAddressInlineSuggested); |
| } |
| |
| // The suggestion already has a plus address - there is nothing to do. |
| return; |
| } |
| |
| RecordAutofillSuggestionEvent( |
| SuggestionEvent::kCreateNewPlusAddressInlineReserveLoadingStateShown); |
| PlusAddressRequestCallback callback = base::BindOnce( |
| [](std::vector<Suggestion> suggestions, size_t suggestion_index, |
| UpdateSuggestionsCallback update_callback, |
| const PlusProfileOrError& profile_or_error) { |
| if (!profile_or_error.has_value()) { |
| suggestions[suggestion_index] = |
| PlusAddressSuggestionGenerator::GetPlusAddressErrorSuggestion( |
| profile_or_error.error()); |
| metrics::RecordAutofillSuggestionEvent( |
| SuggestionEvent::kErrorDuringReserve); |
| std::move(update_callback) |
| .Run(std::move(suggestions), |
| AutofillSuggestionTriggerSource:: |
| kPlusAddressUpdatedInBrowserProcess); |
| return; |
| } |
| PlusAddressSuggestionGenerator::SetSuggestedPlusAddressForSuggestion( |
| profile_or_error->plus_address, suggestions[suggestion_index]); |
| std::move(update_callback) |
| .Run(std::move(suggestions), |
| AutofillSuggestionTriggerSource:: |
| kPlusAddressUpdatedInBrowserProcess); |
| }, |
| std::vector<Suggestion>(current_suggestions.begin(), |
| current_suggestions.end()), |
| it - current_suggestions.begin(), std::move(update_suggestions_callback)); |
| RefreshPlusAddress(primary_main_frame_origin, std::move(callback)); |
| } |
| |
| void PlusAddressServiceImpl::OnAcceptedInlineSuggestion( |
| const url::Origin& primary_main_frame_origin, |
| base::span<const Suggestion> current_suggestions, |
| size_t current_suggestion_index, |
| UpdateSuggestionsCallback update_suggestions_callback, |
| HideSuggestionsCallback hide_suggestions_callback, |
| PlusAddressCallback fill_field_callback, |
| ShowAffiliationErrorDialogCallback show_affiliation_error_dialog, |
| ShowErrorDialogCallback show_error_dialog, |
| base::OnceClosure reshow_suggestions) { |
| RecordAutofillSuggestionEvent( |
| SuggestionEvent::kCreateNewPlusAddressInlineChosen); |
| const std::u16string suggested_address = |
| current_suggestions[current_suggestion_index] |
| .GetPayload<Suggestion::PlusAddressPayload>() |
| .address.value(); |
| PlusAddress requested_plus_address(base::UTF16ToUTF8(suggested_address)); |
| |
| // First, update the suggestions to show a loading state. |
| std::vector<Suggestion> updated_suggestions(current_suggestions.begin(), |
| current_suggestions.end()); |
| PlusAddressSuggestionGenerator::SetLoadingStateForSuggestion( |
| /*is_loading=*/true, updated_suggestions[current_suggestion_index]); |
| std::move(update_suggestions_callback) |
| .Run( |
| std::move(updated_suggestions), |
| AutofillSuggestionTriggerSource::kPlusAddressUpdatedInBrowserProcess); |
| |
| ConfirmPlusAddress( |
| primary_main_frame_origin, std::move(requested_plus_address), |
| base::BindOnce(&PlusAddressServiceImpl::OnConfirmInlineCreation, |
| base::Unretained(this), |
| std::move(hide_suggestions_callback), |
| std::move(fill_field_callback), |
| std::move(show_affiliation_error_dialog), |
| std::move(show_error_dialog), |
| std::move(reshow_suggestions), requested_plus_address)); |
| } |
| |
| std::map<std::string, std::string> |
| PlusAddressServiceImpl::GetPlusAddressHatsData() const { |
| auto time_pref_to_string = [&](std::string_view pref) { |
| const base::Time time = pref_service_->GetTime(pref); |
| if (time.is_null()) { |
| return std::string("-1"); |
| } |
| const base::TimeDelta delta = base::Time::Now() - time; |
| return delta.is_positive() ? base::ToString(delta.InSeconds()) |
| : std::string("-1"); |
| }; |
| |
| return {{hats::kFirstPlusAddressCreationTime, |
| time_pref_to_string(prefs::kFirstPlusAddressCreationTime)}, |
| {hats::kLastPlusAddressFillingTime, |
| time_pref_to_string(prefs::kLastPlusAddressFillingTime)}}; |
| } |
| |
| void PlusAddressServiceImpl::OnConfirmInlineCreation( |
| HideSuggestionsCallback hide_callback, |
| PlusAddressCallback fill_callback, |
| ShowAffiliationErrorDialogCallback show_affiliation_error, |
| ShowErrorDialogCallback show_error, |
| base::OnceClosure reshow_suggestions, |
| const PlusAddress& requested_address, |
| const PlusProfileOrError& profile_or_error) { |
| // Always hide the popup. |
| std::move(hide_callback) |
| .Run(autofill::SuggestionHidingReason::kAcceptSuggestion); |
| |
| if (profile_or_error.has_value()) { |
| // The returned address was not the requested one. This means that there |
| // must already exist an address for an affiliated domain. |
| if (requested_address != profile_or_error->plus_address) { |
| std::move(show_affiliation_error) |
| .Run(GetOriginForDisplay(*profile_or_error), |
| base::UTF8ToUTF16(profile_or_error->plus_address.value())); |
| return; |
| } |
| std::move(fill_callback).Run(profile_or_error->plus_address.value()); |
| return; |
| } |
| |
| if (profile_or_error.error().IsQuotaError()) { |
| std::move(show_error) |
| .Run(PlusAddressErrorDialogType::kQuotaExhausted, |
| /*on_accepted=*/base::DoNothing()); |
| return; |
| } |
| std::move(show_error) |
| .Run(profile_or_error.error().IsTimeoutError() |
| ? PlusAddressErrorDialogType::kTimeout |
| : PlusAddressErrorDialogType::kGenericError, |
| /*on_accepted=*/std::move(reshow_suggestions)); |
| return; |
| } |
| |
| } // namespace plus_addresses |