#include "components/subresource_filter/content/common/ad_delay_throttle.h"

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "url/gurl.h"
#include "url/url_constants.h"

namespace subresource_filter {

namespace {

void LogSecureInfo(AdDelayThrottle::SecureInfo info) {
  UMA_HISTOGRAM_ENUMERATION("Ads.Features.ResourceIsSecure", info);
}

class InsecureCondition : public AdDelayThrottle::DeferCondition {
 public:
  InsecureCondition(base::TimeDelta delay,
                    AdDelayThrottle::MetadataProvider* provider)
      : DeferCondition(delay, provider) {}
  ~InsecureCondition() override {
    if (provider()->IsAdRequest()) {
      LogSecureInfo(was_condition_ever_satisfied()
                        ? AdDelayThrottle::SecureInfo::kInsecureAd
                        : AdDelayThrottle::SecureInfo::kSecureAd);
    } else {
      LogSecureInfo(was_condition_ever_satisfied()
                        ? AdDelayThrottle::SecureInfo::kInsecureNonAd
                        : AdDelayThrottle::SecureInfo::kSecureNonAd);
    }
  }

 private:

  bool IsConditionSatisfied(const GURL& url) override {

    return url.SchemeIs(url::kHttpScheme);
  }
};

class NonIsolatedCondition : public AdDelayThrottle::DeferCondition {
 public:
  NonIsolatedCondition(base::TimeDelta delay,
                       AdDelayThrottle::MetadataProvider* provider)
      : DeferCondition(delay, provider) {}
  ~NonIsolatedCondition() override {
    if (provider()->IsAdRequest()) {
      UMA_HISTOGRAM_ENUMERATION(
          "Ads.Features.AdResourceIsIsolated",
          was_condition_ever_satisfied()
              ? AdDelayThrottle::IsolatedInfo::kNonIsolatedAd
              : AdDelayThrottle::IsolatedInfo::kIsolatedAd);
    }
  }

 private:

  bool IsConditionSatisfied(const GURL& url) override {
    return provider()->RequestIsInNonIsolatedSubframe();
  }
};

};  

AdDelayThrottle::DeferCondition::DeferCondition(
    base::TimeDelta delay,
    AdDelayThrottle::MetadataProvider* provider)
    : delay_(delay), provider_(provider) {
  DCHECK(provider);
}
AdDelayThrottle::DeferCondition::~DeferCondition() = default;

bool AdDelayThrottle::DeferCondition::ShouldDefer(const GURL& url) {
  if (was_condition_applied_) {
    DCHECK(was_condition_ever_satisfied_);
    return false;
  }
  was_condition_ever_satisfied_ |= IsConditionSatisfied(url);
  return was_condition_ever_satisfied_;
}

base::TimeDelta AdDelayThrottle::DeferCondition::OnReadyToDefer() {
  DCHECK(!was_condition_applied_);
  DCHECK(was_condition_ever_satisfied_);
  was_condition_applied_ = true;
  return delay_;
}

constexpr base::TimeDelta AdDelayThrottle::kDefaultDelay;

AdDelayThrottle::Factory::Factory()
    : delay_enabled_(base::FeatureList::IsEnabled(kAdTagging) &&
                     base::FeatureList::IsEnabled(kDelayUnsafeAds)),
      insecure_delay_(base::TimeDelta::FromMilliseconds(
          delay_enabled_ ? base::GetFieldTrialParamByFeatureAsInt(
                               kDelayUnsafeAds,
                               kInsecureDelayParam,
                               kDefaultDelay.InMilliseconds())
                         : 0)),
      non_isolated_delay_(base::TimeDelta::FromMilliseconds(
          delay_enabled_ ? base::GetFieldTrialParamByFeatureAsInt(
                               kDelayUnsafeAds,
                               kNonIsolatedDelayParam,
                               kDefaultDelay.InMilliseconds())
                         : 0)) {}

AdDelayThrottle::Factory::~Factory() = default;

std::unique_ptr<AdDelayThrottle> AdDelayThrottle::Factory::MaybeCreate(
    std::unique_ptr<AdDelayThrottle::MetadataProvider> provider) const {
  DCHECK(provider);
  return base::WrapUnique(new AdDelayThrottle(std::move(provider), this));
}

AdDelayThrottle::~AdDelayThrottle() {
  if (!expected_delay_.is_zero()) {
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay", actual_delay_);
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay.Expected",
                        expected_delay_);
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay.Queuing",
                        actual_delay_ - expected_delay_);
  }
}

void AdDelayThrottle::DetachFromCurrentSequence() {

  DCHECK(!weak_factory_.HasWeakPtrs());
}

void AdDelayThrottle::WillStartRequest(network::ResourceRequest* request,
                                       bool* defer) {
  *defer = MaybeDefer(request->url);
}

void AdDelayThrottle::WillRedirectRequest(
    const net::RedirectInfo& redirect_info,
    const network::ResourceResponseHead& response_head,
    bool* defer) {

  *defer = MaybeDefer(redirect_info.new_url);
}

bool AdDelayThrottle::MaybeDefer(const GURL& url) {

  std::vector<DeferCondition*> matched_conditions;
  for (auto& condition : defer_conditions_) {
    if (condition->ShouldDefer(url))
      matched_conditions.push_back(condition.get());
  }

  if (!delay_enabled_ || matched_conditions.empty() ||
      !provider_->IsAdRequest()) {
    return false;
  }

  base::TimeDelta delay;
  for (DeferCondition* condition : matched_conditions) {
    delay += condition->OnReadyToDefer();
  }

  expected_delay_ += delay;
  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&AdDelayThrottle::Resume, weak_factory_.GetWeakPtr(),
                     tick_clock_->NowTicks()),
      delay);
  return true;
}

void AdDelayThrottle::Resume(base::TimeTicks defer_start) {
  actual_delay_ += tick_clock_->NowTicks() - defer_start;
  delegate_->Resume();
}

AdDelayThrottle::AdDelayThrottle(std::unique_ptr<MetadataProvider> provider,
                                 const AdDelayThrottle::Factory* factory)
    : provider_(std::move(provider)),
      tick_clock_(base::DefaultTickClock::GetInstance()),
      delay_enabled_(factory->delay_enabled()),
      weak_factory_(this) {
  defer_conditions_.emplace_back(std::make_unique<InsecureCondition>(
      factory->insecure_delay(), provider_.get()));
  defer_conditions_.emplace_back(std::make_unique<NonIsolatedCondition>(
      factory->non_isolated_delay(), provider_.get()));
}

}
// Copyright 2018 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/common/ad_delay_throttle.h"

#include <utility>
#include <vector>

#include "base/bind.h"
#include "base/feature_list.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/field_trial_params.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/default_tick_clock.h"
#include "base/time/tick_clock.h"
#include "components/subresource_filter/core/common/common_features.h"
#include "url/gurl.h"
#include "url/url_constants.h"

namespace subresource_filter {

namespace {

void LogSecureInfo(AdDelayThrottle::SecureInfo info) {
  UMA_HISTOGRAM_ENUMERATION("Ads.Features.ResourceIsSecure", info);
}

class InsecureCondition : public AdDelayThrottle::DeferCondition {
 public:
  InsecureCondition(base::TimeDelta delay,
                    AdDelayThrottle::MetadataProvider* provider)
      : DeferCondition(delay, provider) {}
  ~InsecureCondition() override {
    if (provider()->IsAdRequest()) {
      LogSecureInfo(was_condition_ever_satisfied()
                        ? AdDelayThrottle::SecureInfo::kInsecureAd
                        : AdDelayThrottle::SecureInfo::kSecureAd);
    } else {
      LogSecureInfo(was_condition_ever_satisfied()
                        ? AdDelayThrottle::SecureInfo::kInsecureNonAd
                        : AdDelayThrottle::SecureInfo::kSecureNonAd);
    }
  }

 private:
  // DeferCondition:
  bool IsConditionSatisfied(const GURL& url) override {
    // Note: this should probably be using content::IsOriginSecure which
    // accounts for things like whitelisted origins, localhost, etc. This isn't
    // used here because that function is quite expensive for insecure schemes,
    // involving many allocations and string scans.
    return url.SchemeIs(url::kHttpScheme);
  }
};

class NonIsolatedCondition : public AdDelayThrottle::DeferCondition {
 public:
  NonIsolatedCondition(base::TimeDelta delay,
                       AdDelayThrottle::MetadataProvider* provider)
      : DeferCondition(delay, provider) {}
  ~NonIsolatedCondition() override {
    if (provider()->IsAdRequest()) {
      UMA_HISTOGRAM_ENUMERATION(
          "Ads.Features.AdResourceIsIsolated",
          was_condition_ever_satisfied()
              ? AdDelayThrottle::IsolatedInfo::kNonIsolatedAd
              : AdDelayThrottle::IsolatedInfo::kIsolatedAd);
    }
  }

 private:
  // DeferCondition:
  bool IsConditionSatisfied(const GURL& url) override {
    return provider()->RequestIsInNonIsolatedSubframe();
  }
};

};  // namespace

AdDelayThrottle::DeferCondition::DeferCondition(
    base::TimeDelta delay,
    AdDelayThrottle::MetadataProvider* provider)
    : delay_(delay), provider_(provider) {
  DCHECK(provider);
}
AdDelayThrottle::DeferCondition::~DeferCondition() = default;

bool AdDelayThrottle::DeferCondition::ShouldDefer(const GURL& url) {
  if (was_condition_applied_) {
    DCHECK(was_condition_ever_satisfied_);
    return false;
  }
  was_condition_ever_satisfied_ |= IsConditionSatisfied(url);
  return was_condition_ever_satisfied_;
}

// The request will be deferred. Returns the amount of time to defer.
base::TimeDelta AdDelayThrottle::DeferCondition::OnReadyToDefer() {
  DCHECK(!was_condition_applied_);
  DCHECK(was_condition_ever_satisfied_);
  was_condition_applied_ = true;
  return delay_;
}

constexpr base::TimeDelta AdDelayThrottle::kDefaultDelay;

AdDelayThrottle::Factory::Factory()
    : delay_enabled_(base::FeatureList::IsEnabled(kAdTagging) &&
                     base::FeatureList::IsEnabled(kDelayUnsafeAds)),
      insecure_delay_(base::TimeDelta::FromMilliseconds(
          delay_enabled_ ? base::GetFieldTrialParamByFeatureAsInt(
                               kDelayUnsafeAds,
                               kInsecureDelayParam,
                               kDefaultDelay.InMilliseconds())
                         : 0)),
      non_isolated_delay_(base::TimeDelta::FromMilliseconds(
          delay_enabled_ ? base::GetFieldTrialParamByFeatureAsInt(
                               kDelayUnsafeAds,
                               kNonIsolatedDelayParam,
                               kDefaultDelay.InMilliseconds())
                         : 0)) {}

AdDelayThrottle::Factory::~Factory() = default;

std::unique_ptr<AdDelayThrottle> AdDelayThrottle::Factory::MaybeCreate(
    std::unique_ptr<AdDelayThrottle::MetadataProvider> provider) const {
  DCHECK(provider);
  return base::WrapUnique(new AdDelayThrottle(std::move(provider), this));
}

AdDelayThrottle::~AdDelayThrottle() {
  if (!expected_delay_.is_zero()) {
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay", actual_delay_);
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay.Expected",
                        expected_delay_);
    UMA_HISTOGRAM_TIMES("SubresourceFilter.AdDelay.Delay.Queuing",
                        actual_delay_ - expected_delay_);
  }
}

void AdDelayThrottle::DetachFromCurrentSequence() {
  // The throttle is moving to another thread. Ensure this is done before any
  // weak pointers are created.
  DCHECK(!weak_factory_.HasWeakPtrs());
}

void AdDelayThrottle::WillStartRequest(network::ResourceRequest* request,
                                       bool* defer) {
  *defer = MaybeDefer(request->url);
}

void AdDelayThrottle::WillRedirectRequest(
    const net::RedirectInfo& redirect_info,
    const network::ResourceResponseHead& response_head,
    bool* defer) {
  // Note: some MetadataProviders may not be able to distinguish requests that
  // are only tagged as ads after a redirect.
  *defer = MaybeDefer(redirect_info.new_url);
}

bool AdDelayThrottle::MaybeDefer(const GURL& url) {
  // Check for condition matching before checking if the feature is enabled, to
  // ensure metrics can be reported.
  std::vector<DeferCondition*> matched_conditions;
  for (auto& condition : defer_conditions_) {
    if (condition->ShouldDefer(url))
      matched_conditions.push_back(condition.get());
  }

  if (!delay_enabled_ || matched_conditions.empty() ||
      !provider_->IsAdRequest()) {
    return false;
  }

  base::TimeDelta delay;
  for (DeferCondition* condition : matched_conditions) {
    delay += condition->OnReadyToDefer();
  }
  // TODO(csharrison): Consider logging to the console here that Chrome
  // delayed this request.
  expected_delay_ += delay;
  base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&AdDelayThrottle::Resume, weak_factory_.GetWeakPtr(),
                     tick_clock_->NowTicks()),
      delay);
  return true;
}

void AdDelayThrottle::Resume(base::TimeTicks defer_start) {
  actual_delay_ += tick_clock_->NowTicks() - defer_start;
  delegate_->Resume();
}

AdDelayThrottle::AdDelayThrottle(std::unique_ptr<MetadataProvider> provider,
                                 const AdDelayThrottle::Factory* factory)
    : provider_(std::move(provider)),
      tick_clock_(base::DefaultTickClock::GetInstance()),
      delay_enabled_(factory->delay_enabled()),
      weak_factory_(this) {
  defer_conditions_.emplace_back(std::make_unique<InsecureCondition>(
      factory->insecure_delay(), provider_.get()));
  defer_conditions_.emplace_back(std::make_unique<NonIsolatedCondition>(
      factory->non_isolated_delay(), provider_.get()));
}

}  // namespace subresource_filter
