// Copyright (c) 2012 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 "content/browser/loader/throttling_resource_handler.h"

#include "content/browser/loader/resource_request_info_impl.h"
#include "content/public/browser/resource_throttle.h"
#include "content/public/common/resource_response.h"
#include "net/url_request/url_request.h"

namespace content {

ThrottlingResourceHandler::ThrottlingResourceHandler(
    scoped_ptr<ResourceHandler> next_handler,
    net::URLRequest* request,
    ScopedVector<ResourceThrottle> throttles)
    : LayeredResourceHandler(request, next_handler.Pass()),
      deferred_stage_(DEFERRED_NONE),
      throttles_(throttles.Pass()),
      next_index_(0),
      cancelled_by_resource_throttle_(false) {
  for (size_t i = 0; i < throttles_.size(); ++i) {
    throttles_[i]->set_controller(this);
    // Throttles must have a name, as otherwise, bugs where a throttle fails
    // to resume a request can be very difficult to debug.
    DCHECK(throttles_[i]->GetNameForLogging());
  }
}

ThrottlingResourceHandler::~ThrottlingResourceHandler() {
}

bool ThrottlingResourceHandler::OnRequestRedirected(const GURL& new_url,
                                                    ResourceResponse* response,
                                                    bool* defer) {
  DCHECK(!cancelled_by_resource_throttle_);

  *defer = false;
  while (next_index_ < throttles_.size()) {
    int index = next_index_;
    throttles_[index]->WillRedirectRequest(new_url, defer);
    next_index_++;
    if (cancelled_by_resource_throttle_)
      return false;
    if (*defer) {
      OnRequestDefered(index);
      deferred_stage_ = DEFERRED_REDIRECT;
      deferred_url_ = new_url;
      deferred_response_ = response;
      return true;  // Do not cancel.
    }
  }

  next_index_ = 0;  // Reset for next time.

  return next_handler_->OnRequestRedirected(new_url, response, defer);
}

bool ThrottlingResourceHandler::OnWillStart(const GURL& url, bool* defer) {
  DCHECK(!cancelled_by_resource_throttle_);

  *defer = false;
  while (next_index_ < throttles_.size()) {
    int index = next_index_;
    throttles_[index]->WillStartRequest(defer);
    next_index_++;
    if (cancelled_by_resource_throttle_)
      return false;
    if (*defer) {
      OnRequestDefered(index);
      deferred_stage_ = DEFERRED_START;
      deferred_url_ = url;
      return true;  // Do not cancel.
    }
  }

  next_index_ = 0;  // Reset for next time.

  return next_handler_->OnWillStart(url, defer);
}

bool ThrottlingResourceHandler::OnBeforeNetworkStart(const GURL& url,
                                                     bool* defer) {
  DCHECK(!cancelled_by_resource_throttle_);

  *defer = false;
  while (next_index_ < throttles_.size()) {
    int index = next_index_;
    throttles_[index]->OnBeforeNetworkStart(defer);
    next_index_++;
    if (cancelled_by_resource_throttle_)
      return false;
    if (*defer) {
      OnRequestDefered(index);
      deferred_stage_ = DEFERRED_NETWORK_START;
      deferred_url_ = url;
      return true;  // Do not cancel.
    }
  }

  next_index_ = 0;  // Reset for next time.

  return next_handler_->OnBeforeNetworkStart(url, defer);
}

bool ThrottlingResourceHandler::OnResponseStarted(ResourceResponse* response,
                                                  bool* defer) {
  DCHECK(!cancelled_by_resource_throttle_);

  while (next_index_ < throttles_.size()) {
    int index = next_index_;
    throttles_[index]->WillProcessResponse(defer);
    next_index_++;
    if (cancelled_by_resource_throttle_)
      return false;
    if (*defer) {
      OnRequestDefered(index);
      deferred_stage_ = DEFERRED_RESPONSE;
      deferred_response_ = response;
      return true;  // Do not cancel.
    }
  }

  next_index_ = 0;  // Reset for next time.

  return next_handler_->OnResponseStarted(response, defer);
}

void ThrottlingResourceHandler::Cancel() {
  cancelled_by_resource_throttle_ = true;
  controller()->Cancel();
}

void ThrottlingResourceHandler::CancelAndIgnore() {
  cancelled_by_resource_throttle_ = true;
  controller()->CancelAndIgnore();
}

void ThrottlingResourceHandler::CancelWithError(int error_code) {
  cancelled_by_resource_throttle_ = true;
  controller()->CancelWithError(error_code);
}

void ThrottlingResourceHandler::Resume() {
  DCHECK(!cancelled_by_resource_throttle_);

  DeferredStage last_deferred_stage = deferred_stage_;
  deferred_stage_ = DEFERRED_NONE;
  // Clear information about the throttle that delayed the request.
  request()->LogUnblocked();
  switch (last_deferred_stage) {
    case DEFERRED_NONE:
      NOTREACHED();
      break;
    case DEFERRED_START:
      ResumeStart();
      break;
    case DEFERRED_NETWORK_START:
      ResumeNetworkStart();
      break;
    case DEFERRED_REDIRECT:
      ResumeRedirect();
      break;
    case DEFERRED_RESPONSE:
      ResumeResponse();
      break;
  }
}

void ThrottlingResourceHandler::ResumeStart() {
  DCHECK(!cancelled_by_resource_throttle_);

  GURL url = deferred_url_;
  deferred_url_ = GURL();

  bool defer = false;
  if (!OnWillStart(url, &defer)) {
    controller()->Cancel();
  } else if (!defer) {
    controller()->Resume();
  }
}

void ThrottlingResourceHandler::ResumeNetworkStart() {
  DCHECK(!cancelled_by_resource_throttle_);

  GURL url = deferred_url_;
  deferred_url_ = GURL();

  bool defer = false;
  if (!OnBeforeNetworkStart(url, &defer)) {
    controller()->Cancel();
  } else if (!defer) {
    controller()->Resume();
  }
}

void ThrottlingResourceHandler::ResumeRedirect() {
  DCHECK(!cancelled_by_resource_throttle_);

  GURL new_url = deferred_url_;
  deferred_url_ = GURL();
  scoped_refptr<ResourceResponse> response;
  deferred_response_.swap(response);

  bool defer = false;
  if (!OnRequestRedirected(new_url, response.get(), &defer)) {
    controller()->Cancel();
  } else if (!defer) {
    controller()->Resume();
  }
}

void ThrottlingResourceHandler::ResumeResponse() {
  DCHECK(!cancelled_by_resource_throttle_);

  scoped_refptr<ResourceResponse> response;
  deferred_response_.swap(response);

  bool defer = false;
  if (!OnResponseStarted(response.get(), &defer)) {
    controller()->Cancel();
  } else if (!defer) {
    controller()->Resume();
  }
}

void ThrottlingResourceHandler::OnRequestDefered(int throttle_index) {
  request()->LogBlockedBy(throttles_[throttle_index]->GetNameForLogging());
}

}  // namespace content
