chromium/ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.mm

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

#import "ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_tab_helper.h"

#import "components/prefs/pref_service.h"
#import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_notification_result.h"
#import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service.h"
#import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_observer_util.h"
#import "components/safe_browsing/core/browser/tailored_security_service/tailored_security_service_util.h"
#import "components/safe_browsing/core/common/safe_browsing_prefs.h"
#import "ios/chrome/browser/infobars/model/infobar_ios.h"
#import "ios/chrome/browser/infobars/model/infobar_manager_impl.h"
#import "ios/chrome/browser/safe_browsing/model/tailored_security/tailored_security_service_infobar_delegate.h"
#import "ios/chrome/browser/shared/model/profile/profile_ios.h"
#import "ios/chrome/browser/sync/model/sync_service_factory.h"
#import "ios/components/security_interstitials/safe_browsing/safe_browsing_tab_helper.h"
#import "ios/web/public/navigation/navigation_context.h"

#pragma mark - TailoredSecurityTabHelper

TailoredSecurityTabHelper::TailoredSecurityTabHelper(
    web::WebState* web_state,
    safe_browsing::TailoredSecurityService* service)
    : service_(service), web_state_(web_state) {
  bool focused = false;

  if (service_) {
    service_->AddObserver(this);
  }

  if (web_state_) {
    web_state_->AddObserver(this);
    focused = web_state_->IsVisible();
    UpdateFocusAndURL(focused, web_state_->GetLastCommittedURL());
  }
}

TailoredSecurityTabHelper::~TailoredSecurityTabHelper() {
  if (service_) {
    service_->RemoveObserver(this);
    if (has_query_request_) {
      service_->RemoveQueryRequest();
      has_query_request_ = false;
    }
  }
}

WEB_STATE_USER_DATA_KEY_IMPL(TailoredSecurityTabHelper)

#pragma mark - TailoredSecurityServiceObserver

void TailoredSecurityTabHelper::OnTailoredSecurityBitChanged(
    bool enabled,
    base::Time previous_update) {
  if (!enabled || !web_state_->IsVisible()) {
    return;
  }
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForBrowserState(browser_state);
  if (!safe_browsing::CanShowUnconsentedTailoredSecurityDialog(
          sync_service, browser_state->GetPrefs())) {
    return;
  }

  if (base::Time::Now() - previous_update <=
      safe_browsing::kThresholdForInFlowNotification) {
    browser_state->GetPrefs()->SetBoolean(
        prefs::kAccountTailoredSecurityShownNotification, true);
    ShowInfoBar(safe_browsing::TailoredSecurityServiceMessageState::
                    kUnconsentedAndFlowEnabled);
  }
}

void TailoredSecurityTabHelper::OnTailoredSecurityServiceDestroyed() {
  service_->RemoveObserver(this);
  service_ = nullptr;
}

void TailoredSecurityTabHelper::OnSyncNotificationMessageRequest(
    bool is_enabled) {
  // Notification shouldn't show for non-visible WebStates.
  if (!web_state_->IsVisible()) {
    return;
  }

  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
  SetSafeBrowsingState(
      browser_state->GetPrefs(),
      is_enabled ? safe_browsing::SafeBrowsingState::ENHANCED_PROTECTION
                 : safe_browsing::SafeBrowsingState::STANDARD_PROTECTION,
      /*is_esb_enabled_in_sync=*/is_enabled);

  if (is_enabled) {
    ShowInfoBar(safe_browsing::TailoredSecurityServiceMessageState::
                    kConsentedAndFlowEnabled);
  } else {
    ShowInfoBar(safe_browsing::TailoredSecurityServiceMessageState::
                    kConsentedAndFlowDisabled);
  }

  if (is_enabled) {
    safe_browsing::RecordEnabledNotificationResult(
        TailoredSecurityNotificationResult::kShown);
  }
}

#pragma mark - web::WebStateObserver
void TailoredSecurityTabHelper::DidFinishNavigation(
    web::WebState* web_state,
    web::NavigationContext* navigation_context) {
  bool sameDocumentNavigation = navigation_context->IsSameDocument();
  if (!sameDocumentNavigation) {
    UpdateFocusAndURL(true, navigation_context->GetUrl());
  }
}

void TailoredSecurityTabHelper::WasShown(web::WebState* web_state) {
  UpdateFocusAndURL(true, last_url_);
}

void TailoredSecurityTabHelper::WasHidden(web::WebState* web_state) {
  UpdateFocusAndURL(false, last_url_);
}

void TailoredSecurityTabHelper::WebStateDestroyed(web::WebState* web_state) {
  web_state->RemoveObserver(this);
  web_state_ = nullptr;
}

#pragma mark - infobars::InfoBarManager::Observer

void TailoredSecurityTabHelper::OnInfoBarRemoved(infobars::InfoBar* infobar,
                                                 bool animate) {
  if (infobar == infobar_) {
    infobar_manager_scoped_observation_.Reset();
    infobar_ = nullptr;
  }
}

#pragma mark - Private methods

void TailoredSecurityTabHelper::UpdateFocusAndURL(bool focused,
                                                  const GURL& url) {
  DCHECK(web_state_);
  ChromeBrowserState* browser_state =
      ChromeBrowserState::FromBrowserState(web_state_->GetBrowserState());
  syncer::SyncService* sync_service =
      SyncServiceFactory::GetForBrowserState(browser_state);
  if (!safe_browsing::CanShowUnconsentedTailoredSecurityDialog(
          sync_service, browser_state->GetPrefs())) {
    return;
  }

  if (service_) {
    bool should_query =
        focused && safe_browsing::CanQueryTailoredSecurityForUrl(url);
    bool old_should_query =
        focused_ && safe_browsing::CanQueryTailoredSecurityForUrl(last_url_);
    if (should_query && !old_should_query) {
      has_query_request_ = service_->AddQueryRequest();
    }
    if (!should_query && old_should_query && has_query_request_) {
      service_->RemoveQueryRequest();
      has_query_request_ = false;
    }
  }

  focused_ = focused;
  last_url_ = url;
}

void TailoredSecurityTabHelper::ShowInfoBar(
    safe_browsing::TailoredSecurityServiceMessageState message_state) {
  infobars::InfoBarManager* infobar_manager =
      InfoBarManagerImpl::FromWebState(web_state_);
  if (infobar_) {
    // Previous infobars can continue to exist if the infobar was dismissed
    // without any user action. For example, this happens when an infobar has an
    // expired dismissal. Therefore, we remove it to ensure the new infobar is
    // properly observed.
    infobar_manager->RemoveInfoBar(infobar_);
    DCHECK(!infobar_);
  }
  infobar_manager_scoped_observation_.Observe(infobar_manager);

  std::unique_ptr<safe_browsing::TailoredSecurityServiceInfobarDelegate>
      delegate = std::make_unique<
          safe_browsing::TailoredSecurityServiceInfobarDelegate>(message_state,
                                                                 web_state_);

  std::unique_ptr<infobars::InfoBar> infobar = std::make_unique<InfoBarIOS>(
      InfobarType::kInfobarTypeTailoredSecurityService, std::move(delegate));
  infobar_ = infobar_manager->AddInfoBar(std::move(infobar),
                                         /*replace_existing=*/true);
}