// Copyright 2019 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/components/security_interstitials/ios_blocking_page_tab_helper.h"

#import "base/logging.h"
#import "base/strings/string_number_conversions.h"
#import "base/values.h"
#import "ios/components/security_interstitials/ios_security_interstitial_page.h"
#import "ios/web/public/navigation/navigation_context.h"
#import "ios/web/public/web_state.h"
#import "ios/web/public/web_state_user_data.h"

namespace security_interstitials {

#pragma mark - IOSBlockingPageTabHelper

IOSBlockingPageTabHelper::IOSBlockingPageTabHelper(web::WebState* web_state)
    : navigation_id_listener_(web_state, this) {}

IOSBlockingPageTabHelper::~IOSBlockingPageTabHelper() = default;

void IOSBlockingPageTabHelper::AssociateBlockingPage(
    int64_t navigation_id,
    std::unique_ptr<IOSSecurityInterstitialPage> blocking_page) {
  if (navigation_id == last_committed_navigation_id_) {
    blocking_page_for_currently_committed_navigation_ =
        std::move(blocking_page);
  } else {
    blocking_pages_for_navigations_[navigation_id] = std::move(blocking_page);
  }
}

bool IOSBlockingPageTabHelper::ShouldDisplayURL() const {
  return blocking_page_for_currently_committed_navigation_->ShouldDisplayURL();
}

IOSSecurityInterstitialPage* IOSBlockingPageTabHelper::GetCurrentBlockingPage()
    const {
  return blocking_page_for_currently_committed_navigation_.get();
}

void IOSBlockingPageTabHelper::OnBlockingPageCommandReceived(
    SecurityInterstitialCommand command) {
  if (!blocking_page_for_currently_committed_navigation_) {
    return;
  }

  blocking_page_for_currently_committed_navigation_->HandleCommand(command);
}

void IOSBlockingPageTabHelper::UpdateForBlockingPageDismissed() {
  blocking_page_for_currently_committed_navigation_->WasDismissed();
}

void IOSBlockingPageTabHelper::UpdateForFinishedNavigation(
    int64_t navigation_id,
    bool committed) {
  if (committed) {
    last_committed_navigation_id_ = navigation_id;
    blocking_page_for_currently_committed_navigation_ =
        std::move(blocking_pages_for_navigations_[navigation_id]);
  }
  blocking_pages_for_navigations_.erase(navigation_id);
}

#pragma mark - IOSBlockingPageTabHelper::CommittedNavigationIDListener

IOSBlockingPageTabHelper::CommittedNavigationIDListener::
    CommittedNavigationIDListener(web::WebState* web_state,
                                  IOSBlockingPageTabHelper* tab_helper)
    : tab_helper_(tab_helper) {
  DCHECK(tab_helper_);
  scoped_observation_.Observe(web_state);
}

IOSBlockingPageTabHelper::CommittedNavigationIDListener::
    ~CommittedNavigationIDListener() = default;

void IOSBlockingPageTabHelper::CommittedNavigationIDListener::
    DidStartNavigation(web::WebState* web_state,
                       web::NavigationContext* navigation_context) {
  IOSSecurityInterstitialPage* page = tab_helper_->GetCurrentBlockingPage();
  if (page && (navigation_context->GetPageTransition() &
               ui::PAGE_TRANSITION_FORWARD_BACK)) {
    // Interstitial page would be the last page shown so looking for this page
    // transition would mean that a user is using the back button and is
    // leaving the interstitial page.
    tab_helper_->UpdateForBlockingPageDismissed();
  }
}

void IOSBlockingPageTabHelper::CommittedNavigationIDListener::
    DidFinishNavigation(web::WebState* web_state,
                        web::NavigationContext* navigation_context) {
  if (navigation_context->IsSameDocument()) {
    return;
  }

  tab_helper_->UpdateForFinishedNavigation(
      navigation_context->GetNavigationId(),
      navigation_context->HasCommitted());

  // Interstitials may change the visibility of the URL or other security state.
  web_state->DidChangeVisibleSecurityState();

  IOSSecurityInterstitialPage* page = tab_helper_->GetCurrentBlockingPage();
  if (!page) {
    // `page` will be null if a IOSSecurityInterstitialPage is not being
    // displayed to the user.
    return;
  }
  page->ShowInfobar();
}

void IOSBlockingPageTabHelper::CommittedNavigationIDListener::WebStateDestroyed(
    web::WebState* web_state) {
  DCHECK(scoped_observation_.IsObservingSource(web_state));
  scoped_observation_.Reset();

  IOSSecurityInterstitialPage* page = tab_helper_->GetCurrentBlockingPage();
  if (page) {
    // Logs closing the tab as `DONT_PROCEED` since an interstitial page is
    // being dismissed.
    tab_helper_->UpdateForBlockingPageDismissed();
  }
}

}  // namespace security_interstitials
