#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"

#include <utility>

#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "components/subresource_filter/content/browser/activation_state_computing_navigation_throttle.h"
#include "components/subresource_filter/content/browser/async_document_subresource_filter.h"
#include "components/subresource_filter/content/browser/navigation_console_logger.h"
#include "components/subresource_filter/content/browser/page_load_statistics.h"
#include "components/subresource_filter/content/browser/subresource_filter_client.h"
#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
#include "components/subresource_filter/content/common/subresource_filter_messages.h"
#include "components/subresource_filter/content/common/subresource_filter_utils.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/console_message_level.h"
#include "net/base/net_errors.h"

namespace subresource_filter {

ContentSubresourceFilterThrottleManager::
    ContentSubresourceFilterThrottleManager(
        SubresourceFilterClient* client,
        VerifiedRulesetDealer::Handle* dealer_handle,
        content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      scoped_observer_(this),
      dealer_handle_(dealer_handle),
      client_(client),
      weak_ptr_factory_(this) {
  SubresourceFilterObserverManager::CreateForWebContents(web_contents);
  scoped_observer_.Add(
      SubresourceFilterObserverManager::FromWebContents(web_contents));
}

ContentSubresourceFilterThrottleManager::
    ~ContentSubresourceFilterThrottleManager() {}

void ContentSubresourceFilterThrottleManager::OnSubresourceFilterGoingAway() {

  scoped_observer_.RemoveAll();
}

void ContentSubresourceFilterThrottleManager::RenderFrameDeleted(
    content::RenderFrameHost* frame_host) {
  activated_frame_hosts_.erase(frame_host);
  ad_frames_.erase(frame_host);
  DestroyRulesetHandleIfNoLongerUsed();
}

void ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation(
    content::NavigationHandle* navigation_handle) {

  content::RenderFrameHost* previous_rfh =
      navigation_handle->GetWebContents()->UnsafeFindFrameByFrameTreeNodeId(
          navigation_handle->GetFrameTreeNodeId());

  bool transferred_ad_frame = false;
  if (previous_rfh && previous_rfh != navigation_handle->GetRenderFrameHost()) {
    auto previous_rfh_it = ad_frames_.find(previous_rfh);
    if (previous_rfh_it != ad_frames_.end()) {
      ad_frames_.erase(previous_rfh_it);
      ad_frames_.insert(navigation_handle->GetRenderFrameHost());
      transferred_ad_frame = true;
    }
  }

  if (navigation_handle->GetNetErrorCode() != net::OK)
    return;

  auto it = ongoing_activation_throttles_.find(navigation_handle);
  if (it == ongoing_activation_throttles_.end())
    return;

  ActivationStateComputingNavigationThrottle* throttle = it->second;
  AsyncDocumentSubresourceFilter* filter = throttle->filter();
  if (!filter)
    return;

  ActivationLevel level = filter->activation_state().activation_level;
  if (level == ActivationLevel::DISABLED)
    return;

  TRACE_EVENT1(
      TRACE_DISABLED_BY_DEFAULT("loading"),
      "ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation",
      "activation_state", filter->activation_state().ToTracedValue());

  throttle->WillSendActivationToRenderer();

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();

  bool is_ad_subframe =
      transferred_ad_frame || base::ContainsKey(ad_frames_, frame_host);
  DCHECK(!is_ad_subframe || !navigation_handle->IsInMainFrame());

  frame_host->Send(new SubresourceFilterMsg_ActivateForNextCommittedLoad(
      frame_host->GetRoutingID(), filter->activation_state(), is_ad_subframe));
}

void ContentSubresourceFilterThrottleManager::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {

  if (!navigation_handle->HasCommitted() ||
      navigation_handle->IsSameDocument()) {
    ongoing_activation_throttles_.erase(navigation_handle);
    return;
  }

  auto throttle_it = ongoing_activation_throttles_.find(navigation_handle);
  std::unique_ptr<AsyncDocumentSubresourceFilter> filter;
  if (throttle_it != ongoing_activation_throttles_.end()) {
    ActivationStateComputingNavigationThrottle* throttle = throttle_it->second;
    CHECK_EQ(navigation_handle, throttle->navigation_handle());
    filter = throttle->ReleaseFilter();
    ongoing_activation_throttles_.erase(throttle_it);
  }

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();
  if (navigation_handle->IsInMainFrame()) {
    current_committed_load_has_notified_disallowed_load_ = false;
    statistics_.reset();
    if (filter) {
      statistics_ =
          std::make_unique<PageLoadStatistics>(filter->activation_state());
      if (filter->activation_state().enable_logging) {
        DCHECK(filter->activation_state().activation_level !=
               ActivationLevel::DISABLED);
        NavigationConsoleLogger::LogMessageOnCommit(
            navigation_handle, content::CONSOLE_MESSAGE_LEVEL_WARNING,
            kActivationConsoleMessage);
      }
    }
    ActivationLevel level = filter ? filter->activation_state().activation_level
                                   : ActivationLevel::DISABLED;
    UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.PageLoad.ActivationState",
                              level, ActivationLevel::LAST);
  }

  if (filter) {
    base::OnceClosure disallowed_callback(base::BindOnce(
        &ContentSubresourceFilterThrottleManager::MaybeCallFirstDisallowedLoad,
        weak_ptr_factory_.GetWeakPtr()));
    filter->set_first_disallowed_load_callback(std::move(disallowed_callback));
    activated_frame_hosts_[frame_host] = std::move(filter);
  } else {
    activated_frame_hosts_.erase(frame_host);

    MaybeActivateSubframeSpecialUrls(navigation_handle);
  }

  DestroyRulesetHandleIfNoLongerUsed();
}

void ContentSubresourceFilterThrottleManager::DidFinishLoad(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url) {
  if (!statistics_ || render_frame_host->GetParent())
    return;
  statistics_->OnDidFinishLoad();
}

bool ContentSubresourceFilterThrottleManager::OnMessageReceived(
    const IPC::Message& message,
    content::RenderFrameHost* render_frame_host) {
  bool handled = true;

  IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(ContentSubresourceFilterThrottleManager,
                                   message, render_frame_host)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_FrameIsAdSubframe,
                        OnFrameIsAdSubframe)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  if (handled)
    return true;

  IPC_BEGIN_MESSAGE_MAP(ContentSubresourceFilterThrottleManager, message)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_DidDisallowFirstSubresource,
                        MaybeCallFirstDisallowedLoad)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_DocumentLoadStatistics,
                        OnDocumentLoadStatistics)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

void ContentSubresourceFilterThrottleManager::OnPageActivationComputed(
    content::NavigationHandle* navigation_handle,
    const ActivationState& activation_state) {
  DCHECK(navigation_handle->IsInMainFrame());
  DCHECK(!navigation_handle->HasCommitted());

  if (activation_state.activation_level == ActivationLevel::DISABLED)
    return;

  auto it = ongoing_activation_throttles_.find(navigation_handle);
  if (it != ongoing_activation_throttles_.end()) {
    it->second->NotifyPageActivationWithRuleset(EnsureRulesetHandle(),
                                                activation_state);
  }
}

void ContentSubresourceFilterThrottleManager::OnSubframeNavigationEvaluated(
    content::NavigationHandle* navigation_handle,
    LoadPolicy load_policy,
    bool is_ad_subframe) {
  DCHECK(!navigation_handle->IsInMainFrame());
  if (!is_ad_subframe)
    return;

  content::RenderFrameHost* starting_rfh =
      navigation_handle->GetWebContents()->UnsafeFindFrameByFrameTreeNodeId(
          navigation_handle->GetFrameTreeNodeId());
  DCHECK(starting_rfh);
  ad_frames_.insert(starting_rfh);
}

void ContentSubresourceFilterThrottleManager::MaybeAppendNavigationThrottles(
    content::NavigationHandle* navigation_handle,
    std::vector<std::unique_ptr<content::NavigationThrottle>>* throttles) {
  DCHECK(!navigation_handle->IsSameDocument());
  if (!dealer_handle_)
    return;
  if (auto filtering_throttle =
          MaybeCreateSubframeNavigationFilteringThrottle(navigation_handle)) {
    throttles->push_back(std::move(filtering_throttle));
  }

  DCHECK(!base::ContainsKey(ongoing_activation_throttles_, navigation_handle));
  if (auto activation_throttle =
          MaybeCreateActivationStateComputingThrottle(navigation_handle)) {
    ongoing_activation_throttles_[navigation_handle] =
        activation_throttle.get();
    throttles->push_back(std::move(activation_throttle));
  }
}

bool ContentSubresourceFilterThrottleManager::CalculateIsAdSubframe(
    content::RenderFrameHost* frame_host,
    LoadPolicy load_policy) {
  DCHECK(frame_host);
  content::RenderFrameHost* parent_frame = frame_host->GetParent();
  DCHECK(parent_frame);

  return load_policy != LoadPolicy::ALLOW ||
         base::ContainsKey(ad_frames_, frame_host) ||
         base::ContainsKey(ad_frames_, parent_frame);
}

bool ContentSubresourceFilterThrottleManager::IsFrameTaggedAsAdForTesting(
    content::RenderFrameHost* frame_host) const {
  return base::ContainsKey(ad_frames_, frame_host);
}

std::unique_ptr<SubframeNavigationFilteringThrottle>
ContentSubresourceFilterThrottleManager::
    MaybeCreateSubframeNavigationFilteringThrottle(
        content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsInMainFrame())
    return nullptr;
  AsyncDocumentSubresourceFilter* parent_filter =
      GetParentFrameFilter(navigation_handle);
  return parent_filter ? std::make_unique<SubframeNavigationFilteringThrottle>(
                             navigation_handle, parent_filter, this)
                       : nullptr;
}

std::unique_ptr<ActivationStateComputingNavigationThrottle>
ContentSubresourceFilterThrottleManager::
    MaybeCreateActivationStateComputingThrottle(
        content::NavigationHandle* navigation_handle) {

  if (navigation_handle->IsInMainFrame()) {
    auto throttle =
        ActivationStateComputingNavigationThrottle::CreateForMainFrame(
            navigation_handle);
    if (base::FeatureList::IsEnabled(kAdTagging)) {
      throttle->NotifyPageActivationWithRuleset(
          EnsureRulesetHandle(), ActivationState(ActivationLevel::DRYRUN));
    }
    return throttle;
  }

  AsyncDocumentSubresourceFilter* parent_filter =
      GetParentFrameFilter(navigation_handle);
  if (!parent_filter)
    return nullptr;
  DCHECK(ruleset_handle_);
  return ActivationStateComputingNavigationThrottle::CreateForSubframe(
      navigation_handle, ruleset_handle_.get(),
      parent_filter->activation_state());
}

AsyncDocumentSubresourceFilter*
ContentSubresourceFilterThrottleManager::GetParentFrameFilter(
    content::NavigationHandle* child_frame_navigation) {
  DCHECK(!child_frame_navigation->IsInMainFrame());
  content::RenderFrameHost* parent = child_frame_navigation->GetParentFrame();
  DCHECK(parent);

  while (parent) {
    auto it = activated_frame_hosts_.find(parent);
    if (it == activated_frame_hosts_.end())
      return nullptr;

    if (it->second)
      return it->second.get();
    parent = it->first->GetParent();
  }

  NOTREACHED();
  return nullptr;
}

void ContentSubresourceFilterThrottleManager::MaybeCallFirstDisallowedLoad() {
  if (current_committed_load_has_notified_disallowed_load_)
    return;

  auto it = activated_frame_hosts_.find(web_contents()->GetMainFrame());
  if (it == activated_frame_hosts_.end() ||
      it->second->activation_state().activation_level !=
          ActivationLevel::ENABLED) {
    return;
  }
  client_->ShowNotification();
  current_committed_load_has_notified_disallowed_load_ = true;
}

VerifiedRuleset::Handle*
ContentSubresourceFilterThrottleManager::EnsureRulesetHandle() {
  if (!ruleset_handle_)
    ruleset_handle_ = std::make_unique<VerifiedRuleset::Handle>(dealer_handle_);
  return ruleset_handle_.get();
}

void ContentSubresourceFilterThrottleManager::
    DestroyRulesetHandleIfNoLongerUsed() {
  if (activated_frame_hosts_.size() + ongoing_activation_throttles_.size() ==
      0u) {
    ruleset_handle_.reset();
  }
}

void ContentSubresourceFilterThrottleManager::OnDocumentLoadStatistics(
    const DocumentLoadStatistics& statistics) {
  if (statistics_)
    statistics_->OnDocumentLoadStatistics(statistics);
}

void ContentSubresourceFilterThrottleManager::OnFrameIsAdSubframe(
    content::RenderFrameHost* render_frame_host) {
  DCHECK(render_frame_host);

  ad_frames_.insert(render_frame_host);
  SubresourceFilterObserverManager::FromWebContents(web_contents())
      ->NotifyAdSubframeDetected(render_frame_host);
}

void ContentSubresourceFilterThrottleManager::MaybeActivateSubframeSpecialUrls(
    content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsInMainFrame())
    return;

  if (!ShouldUseParentActivation(navigation_handle->GetURL()))
    return;

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();
  if (!frame_host)
    return;

  content::RenderFrameHost* parent = navigation_handle->GetParentFrame();
  DCHECK(parent);
  if (base::ContainsKey(activated_frame_hosts_, parent))
    activated_frame_hosts_[frame_host] = nullptr;
}

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

#include "components/subresource_filter/content/browser/content_subresource_filter_throttle_manager.h"

#include <utility>

#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "base/trace_event/trace_event_argument.h"
#include "components/subresource_filter/content/browser/activation_state_computing_navigation_throttle.h"
#include "components/subresource_filter/content/browser/async_document_subresource_filter.h"
#include "components/subresource_filter/content/browser/navigation_console_logger.h"
#include "components/subresource_filter/content/browser/page_load_statistics.h"
#include "components/subresource_filter/content/browser/subresource_filter_client.h"
#include "components/subresource_filter/content/browser/subresource_filter_observer_manager.h"
#include "components/subresource_filter/content/common/subresource_filter_messages.h"
#include "components/subresource_filter/content/common/subresource_filter_utils.h"
#include "components/subresource_filter/core/browser/subresource_filter_constants.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/console_message_level.h"
#include "net/base/net_errors.h"

namespace subresource_filter {

ContentSubresourceFilterThrottleManager::
    ContentSubresourceFilterThrottleManager(
        SubresourceFilterClient* client,
        VerifiedRulesetDealer::Handle* dealer_handle,
        content::WebContents* web_contents)
    : content::WebContentsObserver(web_contents),
      scoped_observer_(this),
      dealer_handle_(dealer_handle),
      client_(client),
      weak_ptr_factory_(this) {
  SubresourceFilterObserverManager::CreateForWebContents(web_contents);
  scoped_observer_.Add(
      SubresourceFilterObserverManager::FromWebContents(web_contents));
}

ContentSubresourceFilterThrottleManager::
    ~ContentSubresourceFilterThrottleManager() {}

void ContentSubresourceFilterThrottleManager::OnSubresourceFilterGoingAway() {
  // Stop observing here because the observer manager could be destroyed by the
  // time this class is destroyed.
  scoped_observer_.RemoveAll();
}

void ContentSubresourceFilterThrottleManager::RenderFrameDeleted(
    content::RenderFrameHost* frame_host) {
  activated_frame_hosts_.erase(frame_host);
  ad_frames_.erase(frame_host);
  DestroyRulesetHandleIfNoLongerUsed();
}

// Pull the AsyncDocumentSubresourceFilter and its associated ActivationState
// out of the activation state computing throttle. Store it for later filtering
// of subframe navigations.
void ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation(
    content::NavigationHandle* navigation_handle) {
  // Since the frame hasn't yet committed, GetCurrentRenderFrameHost() points
  // to the initial RFH.
  // TODO(crbug.com/843646): Use an API that NavigationHandle supports rather
  // than trying to infer what the NavigationHandle is doing.
  content::RenderFrameHost* previous_rfh =
      navigation_handle->GetWebContents()->UnsafeFindFrameByFrameTreeNodeId(
          navigation_handle->GetFrameTreeNodeId());

  // If a known ad RenderFrameHost has moved to a new host, update ad_frames_.
  bool transferred_ad_frame = false;
  if (previous_rfh && previous_rfh != navigation_handle->GetRenderFrameHost()) {
    auto previous_rfh_it = ad_frames_.find(previous_rfh);
    if (previous_rfh_it != ad_frames_.end()) {
      ad_frames_.erase(previous_rfh_it);
      ad_frames_.insert(navigation_handle->GetRenderFrameHost());
      transferred_ad_frame = true;
    }
  }

  if (navigation_handle->GetNetErrorCode() != net::OK)
    return;

  auto it = ongoing_activation_throttles_.find(navigation_handle);
  if (it == ongoing_activation_throttles_.end())
    return;

  // Main frame throttles with disabled page-level activation will not have
  // associated filters.
  ActivationStateComputingNavigationThrottle* throttle = it->second;
  AsyncDocumentSubresourceFilter* filter = throttle->filter();
  if (!filter)
    return;

  // A filter with DISABLED activation indicates a corrupted ruleset.
  ActivationLevel level = filter->activation_state().activation_level;
  if (level == ActivationLevel::DISABLED)
    return;

  TRACE_EVENT1(
      TRACE_DISABLED_BY_DEFAULT("loading"),
      "ContentSubresourceFilterThrottleManager::ReadyToCommitNavigation",
      "activation_state", filter->activation_state().ToTracedValue());

  throttle->WillSendActivationToRenderer();

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();

  bool is_ad_subframe =
      transferred_ad_frame || base::ContainsKey(ad_frames_, frame_host);
  DCHECK(!is_ad_subframe || !navigation_handle->IsInMainFrame());

  frame_host->Send(new SubresourceFilterMsg_ActivateForNextCommittedLoad(
      frame_host->GetRoutingID(), filter->activation_state(), is_ad_subframe));
}

void ContentSubresourceFilterThrottleManager::DidFinishNavigation(
    content::NavigationHandle* navigation_handle) {
  // Do nothing if the navigation finished in the same document. Just make sure
  // to not leak throttle pointers.
  if (!navigation_handle->HasCommitted() ||
      navigation_handle->IsSameDocument()) {
    ongoing_activation_throttles_.erase(navigation_handle);
    return;
  }

  auto throttle_it = ongoing_activation_throttles_.find(navigation_handle);
  std::unique_ptr<AsyncDocumentSubresourceFilter> filter;
  if (throttle_it != ongoing_activation_throttles_.end()) {
    ActivationStateComputingNavigationThrottle* throttle = throttle_it->second;
    CHECK_EQ(navigation_handle, throttle->navigation_handle());
    filter = throttle->ReleaseFilter();
    ongoing_activation_throttles_.erase(throttle_it);
  }

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();
  if (navigation_handle->IsInMainFrame()) {
    current_committed_load_has_notified_disallowed_load_ = false;
    statistics_.reset();
    if (filter) {
      statistics_ =
          std::make_unique<PageLoadStatistics>(filter->activation_state());
      if (filter->activation_state().enable_logging) {
        DCHECK(filter->activation_state().activation_level !=
               ActivationLevel::DISABLED);
        NavigationConsoleLogger::LogMessageOnCommit(
            navigation_handle, content::CONSOLE_MESSAGE_LEVEL_WARNING,
            kActivationConsoleMessage);
      }
    }
    ActivationLevel level = filter ? filter->activation_state().activation_level
                                   : ActivationLevel::DISABLED;
    UMA_HISTOGRAM_ENUMERATION("SubresourceFilter.PageLoad.ActivationState",
                              level, ActivationLevel::LAST);
  }

  // Make sure |activated_frame_hosts_| is updated or cleaned up depending on
  // this navigation's activation state.
  if (filter) {
    base::OnceClosure disallowed_callback(base::BindOnce(
        &ContentSubresourceFilterThrottleManager::MaybeCallFirstDisallowedLoad,
        weak_ptr_factory_.GetWeakPtr()));
    filter->set_first_disallowed_load_callback(std::move(disallowed_callback));
    activated_frame_hosts_[frame_host] = std::move(filter);
  } else {
    activated_frame_hosts_.erase(frame_host);

    // If this is for a special url that did not go through the navigation
    // throttles, then based on the parent's activation state, possibly add this
    // to activated_frame_hosts_.
    MaybeActivateSubframeSpecialUrls(navigation_handle);
  }

  DestroyRulesetHandleIfNoLongerUsed();
}

void ContentSubresourceFilterThrottleManager::DidFinishLoad(
    content::RenderFrameHost* render_frame_host,
    const GURL& validated_url) {
  if (!statistics_ || render_frame_host->GetParent())
    return;
  statistics_->OnDidFinishLoad();
}

bool ContentSubresourceFilterThrottleManager::OnMessageReceived(
    const IPC::Message& message,
    content::RenderFrameHost* render_frame_host) {
  bool handled = true;

  IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(ContentSubresourceFilterThrottleManager,
                                   message, render_frame_host)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_FrameIsAdSubframe,
                        OnFrameIsAdSubframe)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  if (handled)
    return true;

  IPC_BEGIN_MESSAGE_MAP(ContentSubresourceFilterThrottleManager, message)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_DidDisallowFirstSubresource,
                        MaybeCallFirstDisallowedLoad)
    IPC_MESSAGE_HANDLER(SubresourceFilterHostMsg_DocumentLoadStatistics,
                        OnDocumentLoadStatistics)
    IPC_MESSAGE_UNHANDLED(handled = false)
  IPC_END_MESSAGE_MAP()
  return handled;
}

// Sets the desired page-level |activation_state| for the currently ongoing
// page load, identified by its main-frame |navigation_handle|. If this method
// is not called for a main-frame navigation, the default behavior is no
// activation for that page load.
void ContentSubresourceFilterThrottleManager::OnPageActivationComputed(
    content::NavigationHandle* navigation_handle,
    const ActivationState& activation_state) {
  DCHECK(navigation_handle->IsInMainFrame());
  DCHECK(!navigation_handle->HasCommitted());
  // Do not notify the throttle if activation is disabled.
  if (activation_state.activation_level == ActivationLevel::DISABLED)
    return;

  auto it = ongoing_activation_throttles_.find(navigation_handle);
  if (it != ongoing_activation_throttles_.end()) {
    it->second->NotifyPageActivationWithRuleset(EnsureRulesetHandle(),
                                                activation_state);
  }
}

void ContentSubresourceFilterThrottleManager::OnSubframeNavigationEvaluated(
    content::NavigationHandle* navigation_handle,
    LoadPolicy load_policy,
    bool is_ad_subframe) {
  DCHECK(!navigation_handle->IsInMainFrame());
  if (!is_ad_subframe)
    return;

  // TODO(crbug.com/843646): Use an API that NavigationHandle supports rather
  // than trying to infer what the NavigationHandle is doing.
  content::RenderFrameHost* starting_rfh =
      navigation_handle->GetWebContents()->UnsafeFindFrameByFrameTreeNodeId(
          navigation_handle->GetFrameTreeNodeId());
  DCHECK(starting_rfh);
  ad_frames_.insert(starting_rfh);
}

void ContentSubresourceFilterThrottleManager::MaybeAppendNavigationThrottles(
    content::NavigationHandle* navigation_handle,
    std::vector<std::unique_ptr<content::NavigationThrottle>>* throttles) {
  DCHECK(!navigation_handle->IsSameDocument());
  if (!dealer_handle_)
    return;
  if (auto filtering_throttle =
          MaybeCreateSubframeNavigationFilteringThrottle(navigation_handle)) {
    throttles->push_back(std::move(filtering_throttle));
  }

  DCHECK(!base::ContainsKey(ongoing_activation_throttles_, navigation_handle));
  if (auto activation_throttle =
          MaybeCreateActivationStateComputingThrottle(navigation_handle)) {
    ongoing_activation_throttles_[navigation_handle] =
        activation_throttle.get();
    throttles->push_back(std::move(activation_throttle));
  }
}

bool ContentSubresourceFilterThrottleManager::CalculateIsAdSubframe(
    content::RenderFrameHost* frame_host,
    LoadPolicy load_policy) {
  DCHECK(frame_host);
  content::RenderFrameHost* parent_frame = frame_host->GetParent();
  DCHECK(parent_frame);

  return load_policy != LoadPolicy::ALLOW ||
         base::ContainsKey(ad_frames_, frame_host) ||
         base::ContainsKey(ad_frames_, parent_frame);
}

bool ContentSubresourceFilterThrottleManager::IsFrameTaggedAsAdForTesting(
    content::RenderFrameHost* frame_host) const {
  return base::ContainsKey(ad_frames_, frame_host);
}

std::unique_ptr<SubframeNavigationFilteringThrottle>
ContentSubresourceFilterThrottleManager::
    MaybeCreateSubframeNavigationFilteringThrottle(
        content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsInMainFrame())
    return nullptr;
  AsyncDocumentSubresourceFilter* parent_filter =
      GetParentFrameFilter(navigation_handle);
  return parent_filter ? std::make_unique<SubframeNavigationFilteringThrottle>(
                             navigation_handle, parent_filter, this)
                       : nullptr;
}

std::unique_ptr<ActivationStateComputingNavigationThrottle>
ContentSubresourceFilterThrottleManager::
    MaybeCreateActivationStateComputingThrottle(
        content::NavigationHandle* navigation_handle) {
  // Main frames: create unconditionally.
  if (navigation_handle->IsInMainFrame()) {
    auto throttle =
        ActivationStateComputingNavigationThrottle::CreateForMainFrame(
            navigation_handle);
    if (base::FeatureList::IsEnabled(kAdTagging)) {
      throttle->NotifyPageActivationWithRuleset(
          EnsureRulesetHandle(), ActivationState(ActivationLevel::DRYRUN));
    }
    return throttle;
  }

  // Subframes: create only for frames with activated parents.
  AsyncDocumentSubresourceFilter* parent_filter =
      GetParentFrameFilter(navigation_handle);
  if (!parent_filter)
    return nullptr;
  DCHECK(ruleset_handle_);
  return ActivationStateComputingNavigationThrottle::CreateForSubframe(
      navigation_handle, ruleset_handle_.get(),
      parent_filter->activation_state());
}

AsyncDocumentSubresourceFilter*
ContentSubresourceFilterThrottleManager::GetParentFrameFilter(
    content::NavigationHandle* child_frame_navigation) {
  DCHECK(!child_frame_navigation->IsInMainFrame());
  content::RenderFrameHost* parent = child_frame_navigation->GetParentFrame();
  DCHECK(parent);

  // Filter will be null for those special url navigations that were added in
  // MaybeActivateSubframeSpecialUrls. Return the filter of the first parent
  // with a non-null filter.
  while (parent) {
    auto it = activated_frame_hosts_.find(parent);
    if (it == activated_frame_hosts_.end())
      return nullptr;

    if (it->second)
      return it->second.get();
    parent = it->first->GetParent();
  }

  // Since null filter is only possible for special navigations of iframes, the
  // above loop should have found a filter for at least the top level frame,
  // thus making this unreachable.
  NOTREACHED();
  return nullptr;
}

void ContentSubresourceFilterThrottleManager::MaybeCallFirstDisallowedLoad() {
  if (current_committed_load_has_notified_disallowed_load_)
    return;

  // This shouldn't happen normally, but in the rare case that an IPC from a
  // previous page arrives late we should guard against it.
  auto it = activated_frame_hosts_.find(web_contents()->GetMainFrame());
  if (it == activated_frame_hosts_.end() ||
      it->second->activation_state().activation_level !=
          ActivationLevel::ENABLED) {
    return;
  }
  client_->ShowNotification();
  current_committed_load_has_notified_disallowed_load_ = true;
}

VerifiedRuleset::Handle*
ContentSubresourceFilterThrottleManager::EnsureRulesetHandle() {
  if (!ruleset_handle_)
    ruleset_handle_ = std::make_unique<VerifiedRuleset::Handle>(dealer_handle_);
  return ruleset_handle_.get();
}

void ContentSubresourceFilterThrottleManager::
    DestroyRulesetHandleIfNoLongerUsed() {
  if (activated_frame_hosts_.size() + ongoing_activation_throttles_.size() ==
      0u) {
    ruleset_handle_.reset();
  }
}

void ContentSubresourceFilterThrottleManager::OnDocumentLoadStatistics(
    const DocumentLoadStatistics& statistics) {
  if (statistics_)
    statistics_->OnDocumentLoadStatistics(statistics);
}

void ContentSubresourceFilterThrottleManager::OnFrameIsAdSubframe(
    content::RenderFrameHost* render_frame_host) {
  DCHECK(render_frame_host);

  ad_frames_.insert(render_frame_host);
  SubresourceFilterObserverManager::FromWebContents(web_contents())
      ->NotifyAdSubframeDetected(render_frame_host);
}

void ContentSubresourceFilterThrottleManager::MaybeActivateSubframeSpecialUrls(
    content::NavigationHandle* navigation_handle) {
  if (navigation_handle->IsInMainFrame())
    return;

  if (!ShouldUseParentActivation(navigation_handle->GetURL()))
    return;

  content::RenderFrameHost* frame_host =
      navigation_handle->GetRenderFrameHost();
  if (!frame_host)
    return;

  content::RenderFrameHost* parent = navigation_handle->GetParentFrame();
  DCHECK(parent);
  if (base::ContainsKey(activated_frame_hosts_, parent))
    activated_frame_hosts_[frame_host] = nullptr;
}

}  // namespace subresource_filter
