// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// See http://wiki.corp.google.com/twiki/bin/view/Main/ChromeMultiProcessResourceLoading

#include <vector>

#include "chrome/browser/resource_dispatcher_host.h"

#include "base/message_loop.h"
#include "base/scoped_ptr.h"
#include "base/time.h"
#include "chrome/browser/cert_store.h"
#include "chrome/browser/cross_site_request_manager.h"
#include "chrome/browser/download_file.h"
#include "chrome/browser/download_manager.h"
#include "chrome/browser/external_protocol_handler.h"
#include "chrome/browser/login_prompt.h"
#include "chrome/browser/navigation_profiler.h"
#include "chrome/browser/plugin_service.h"
#include "chrome/browser/renderer_security_policy.h"
#include "chrome/browser/render_view_host.h"
#include "chrome/browser/render_view_host_delegate.h"
#include "chrome/browser/resource_request_details.h"
#include "chrome/browser/safe_browsing/safe_browsing_service.h"
#include "chrome/browser/save_file_manager.h"
#include "chrome/browser/tab_contents.h"
#include "chrome/browser/tab_util.h"
#include "chrome/browser/views/info_bar_view.h"
#include "chrome/browser/views/info_bar_message_view.h"
#include "chrome/common/notification_source.h"
#include "chrome/common/notification_types.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/stl_util-inl.h"
#include "net/base/auth.h"
#include "net/base/cert_status_flags.h"
#include "net/base/load_flags.h"
#include "net/base/mime_sniffer.h"
#include "net/base/mime_util.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_request.h"

// Uncomment to enable logging of request traffic.
//#define LOG_RESOURCE_DISPATCHER_REQUESTS

#ifdef LOG_RESOURCE_DISPATCHER_REQUESTS
# define RESOURCE_LOG(stuff) LOG(INFO) << stuff
#else
# define RESOURCE_LOG(stuff)
#endif

// ----------------------------------------------------------------------------

// The interval for calls to ResourceDispatcherHost::UpdateLoadStates
static const int kUpdateLoadStatesIntervalMsec = 100;

// Maximum number of pending data messages sent to the renderer at any
// given time for a given request.
static const int kMaxPendingDataMessages = 20;

// Maximum time to wait for a gethash response from the Safe Browsing servers.
static const int kMaxGetHashMs = 1000;

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::Response

struct ResourceDispatcherHost::Response : public base::RefCounted<Response> {
  ViewMsg_Resource_ResponseHead response_head;
};

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::AsyncEventHandler

// Used to complete an asynchronous resource request in response to resource
// load events from the resource dispatcher host.
class ResourceDispatcherHost::AsyncEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  AsyncEventHandler(ResourceDispatcherHost::Receiver* receiver,
                    int render_process_host_id,
                    int routing_id,
                    HANDLE render_process,
                    const GURL& url,
                    ResourceDispatcherHost* resource_dispatcher_host)
      : receiver_(receiver),
        render_process_host_id_(render_process_host_id),
        routing_id_(routing_id),
        render_process_(render_process),
        rdh_(resource_dispatcher_host) { }

  static void GlobalCleanup() {
    delete spare_read_buffer_;
    spare_read_buffer_ = NULL;
  }

  bool OnUploadProgress(int request_id, uint64 position, uint64 size) {
    return receiver_->Send(new ViewMsg_Resource_UploadProgress(
        routing_id_, request_id, position, size));
  }

  bool OnRequestRedirected(int request_id, const GURL& new_url) {
    return receiver_->Send(new ViewMsg_Resource_ReceivedRedirect(
        routing_id_, request_id, new_url));
  }

  bool OnResponseStarted(int request_id, Response* response) {
    receiver_->Send(new ViewMsg_Resource_ReceivedResponse(
        routing_id_, request_id, response->response_head));
    return true;
  }

  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    DCHECK(min_size == -1);
    static const int kReadBufSize = 32768;
    if (spare_read_buffer_) {
      read_buffer_.reset(spare_read_buffer_);
      spare_read_buffer_ = NULL;
    } else {
      read_buffer_.reset(new SharedMemory);
      if (!read_buffer_->Create(std::wstring(), false, false, kReadBufSize))
        return false;
      if (!read_buffer_->Map(kReadBufSize))
        return false;
    }
    *buf = static_cast<char*>(read_buffer_->memory());
    *buf_size = kReadBufSize;
    return true;
  }

  bool OnReadCompleted(int request_id, int* bytes_read) {
    if (!*bytes_read)
      return true;
    DCHECK(read_buffer_.get());

    if (!rdh_->WillSendData(render_process_host_id_, request_id)) {
      // We should not send this data now, we have too many pending requests.
      return true;
    }

    SharedMemoryHandle handle;
    if (!read_buffer_->GiveToProcess(render_process_, &handle)) {
      // We wrongfully incremented the pending data count. Fake an ACK message
      // to fix this. We can't move this call above the WillSendData because
      // it's killing our read_buffer_, and we don't want that when we pause
      // the request.
      rdh_->OnDataReceivedACK(render_process_host_id_, request_id);
      return false;
    }

    receiver_->Send(new ViewMsg_Resource_DataReceived(
        routing_id_, request_id, handle, *bytes_read));

    return true;
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    receiver_->Send(new ViewMsg_Resource_RequestComplete(
        routing_id_, request_id, status));

    // If we still have a read buffer, then see about caching it for later...
    if (spare_read_buffer_) {
      read_buffer_.reset();
    } else if (read_buffer_.get() && read_buffer_->memory()) {
      spare_read_buffer_ = read_buffer_.release();
    }
    return true;
  }

 private:
  // When reading, we don't know if we are going to get EOF (0 bytes read), so
  // we typically have a buffer that we allocated but did not use.  We keep
  // this buffer around for the next read as a small optimization.
  static SharedMemory* spare_read_buffer_;

  scoped_ptr<SharedMemory> read_buffer_;
  ResourceDispatcherHost::Receiver* receiver_;
  int render_process_host_id_;
  int routing_id_;
  HANDLE render_process_;
  ResourceDispatcherHost* rdh_;
};
SharedMemory* ResourceDispatcherHost::AsyncEventHandler::spare_read_buffer_;

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::SyncEventHandler

// Used to complete a synchronous resource request in response to resource load
// events from the resource dispatcher host.
class ResourceDispatcherHost::SyncEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  SyncEventHandler(ResourceDispatcherHost::Receiver* receiver,
                   const GURL& url,
                   IPC::Message* result_message)
      : receiver_(receiver),
        result_message_(result_message) {
    result_.final_url = url;
    result_.filter_policy = FilterPolicy::DONT_FILTER;
  }

  bool OnRequestRedirected(int request_id, const GURL& new_url) {
    result_.final_url = new_url;
    return true;
  }

  bool OnResponseStarted(int request_id, Response* response) {
    // We don't care about copying the status here.
    result_.headers = response->response_head.headers;
    result_.mime_type = response->response_head.mime_type;
    result_.charset = response->response_head.charset;
    return true;
  }

  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    DCHECK(min_size == -1);
    *buf = read_buffer_;
    *buf_size = kReadBufSize;
    return true;
  }

  bool OnReadCompleted(int request_id, int* bytes_read) {
    if (!*bytes_read)
      return true;
    result_.data.append(read_buffer_, *bytes_read);
    return true;
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    result_.status = status;

    ViewHostMsg_SyncLoad::WriteReplyParams(result_message_, result_);
    receiver_->Send(result_message_);
    return true;
  }

 private:
  enum { kReadBufSize = 3840 };
  char read_buffer_[kReadBufSize];

  ViewHostMsg_SyncLoad_Result result_;
  ResourceDispatcherHost::Receiver* receiver_;
  IPC::Message* result_message_;
};

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::DownloadEventHandler
// Forwards data to the download thread.

class ResourceDispatcherHost::DownloadEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  DownloadEventHandler(ResourceDispatcherHost* rdh,
                       int render_process_host_id,
                       int render_view_id,
                       int request_id,
                       const std::string& url,
                       DownloadFileManager* manager,
                       URLRequest* request,
                       bool save_as)
      : download_id_(-1),
        global_id_(
            ResourceDispatcherHost::GlobalRequestID(render_process_host_id,
                                                    request_id)),
        render_view_id_(render_view_id),
        read_buffer_(NULL),
        url_(UTF8ToWide(url)),
        content_length_(0),
        download_manager_(manager),
        request_(request),
        save_as_(save_as),
        buffer_(new DownloadBuffer),
        rdh_(rdh),
        is_paused_(false),
        pause_timer_(TimeDelta::FromMilliseconds(kThrottleTimeMs)) {
    pause_timer_.set_task(
        NewRunnableMethod(this, &DownloadEventHandler::CheckWriteProgress));
  }

  // Not needed, as this event handler ought to be the final resource.
  bool OnRequestRedirected(int request_id, const GURL& url) {
    url_ = UTF8ToWide(url.spec());
    return true;
  }

  // Send the download creation information to the download thread.
  bool OnResponseStarted(int request_id, Response* response) {
    std::string content_disposition;
    request_->GetResponseHeaderByName("content-disposition",
                                      &content_disposition);
    set_content_disposition(content_disposition);
    set_content_length(response->response_head.content_length);

    download_id_ = download_manager_->GetNextId();
    // |download_manager_| consumes (deletes):
    DownloadCreateInfo* info = new DownloadCreateInfo;
    info->url = url_;
    info->start_time = Time::Now();
    info->received_bytes = 0;
    info->total_bytes = content_length_;
    info->state = DownloadItem::IN_PROGRESS;
    info->download_id = download_id_;
    info->render_process_id = global_id_.render_process_host_id;
    info->render_view_id = render_view_id_;
    info->request_id = global_id_.request_id;
    info->content_disposition = content_disposition_;
    info->mime_type = response->response_head.mime_type;
    info->save_as = save_as_;
    download_manager_->file_loop()->PostTask(FROM_HERE,
        NewRunnableMethod(download_manager_,
                          &DownloadFileManager::StartDownload,
                          info));
    return true;
  }

  // Create a new buffer, which will be handed to the download thread for file
  // writing and deletion.
  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    DCHECK(buf && buf_size);
    if (!read_buffer_) {
      *buf_size = min_size < 0 ? kReadBufSize : min_size;
      read_buffer_ = new char[*buf_size];
    }
    *buf = read_buffer_;
    return true;
  }

  // Pass the buffer to the download file writer.
  bool OnReadCompleted(int request_id, int* bytes_read) {
    if (!*bytes_read)
      return true;
    DCHECK(read_buffer_);
    AutoLock auto_lock(buffer_->lock);
    bool need_update = buffer_->contents.empty();
    buffer_->contents.push_back(std::make_pair(read_buffer_, *bytes_read));
    if (need_update) {
      download_manager_->file_loop()->PostTask(FROM_HERE,
          NewRunnableMethod(download_manager_,
                            &DownloadFileManager::UpdateDownload,
                            download_id_,
                            buffer_));
    }
    read_buffer_ = NULL;

    // We schedule a pause outside of the read loop if there is too much file
    // writing work to do.
    if (buffer_->contents.size() > kLoadsToWrite)
      pause_timer_.Start();

    return true;
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    download_manager_->file_loop()->PostTask(FROM_HERE,
        NewRunnableMethod(download_manager_,
                          &DownloadFileManager::DownloadFinished,
                          download_id_,
                          buffer_));
    delete [] read_buffer_;

    // 'buffer_' is deleted by the DownloadFileManager.
    buffer_ = NULL;
    return true;
  }

  // If the content-length header is not present (or contains something other
  // than numbers), the incoming content_length is -1 (unknown size).
  // Set the content length to 0 to indicate unknown size to DownloadManager.
  void set_content_length(const int64& content_length) {
    content_length_ = 0;
    if (content_length > 0)
      content_length_ = content_length;
  }

  void set_content_disposition(const std::string& content_disposition) {
    content_disposition_ = content_disposition;
  }

  void CheckWriteProgress() {
    if (!buffer_)
      return;  // The download completed while we were waiting to run.

    size_t contents_size;
    {
      AutoLock lock(buffer_->lock);
      contents_size = buffer_->contents.size();
    }

    bool should_pause = contents_size > kLoadsToWrite;

    // We'll come back later and see if it's okay to unpause the request.
    if (should_pause)
      pause_timer_.Start();

    if (is_paused_ != should_pause) {
      rdh_->PauseRequest(global_id_.render_process_host_id,
                         global_id_.request_id,
                         should_pause);
      is_paused_ = should_pause;
    }
  }

 private:
  int download_id_;
  ResourceDispatcherHost::GlobalRequestID global_id_;
  int render_view_id_;
  char* read_buffer_;
  std::string content_disposition_;
  std::wstring url_;
  int64 content_length_;
  DownloadFileManager* download_manager_;
  URLRequest* request_;
  bool save_as_;  // Request was initiated via "Save As" by the user.
  DownloadBuffer* buffer_;
  ResourceDispatcherHost* rdh_;
  bool is_paused_;
  OneShotTimer pause_timer_;

  static const int kReadBufSize = 32768;  // bytes
  static const int kLoadsToWrite = 100;  // number of data buffers queued
  static const int kThrottleTimeMs = 200;  // milliseconds

  DISALLOW_EVIL_CONSTRUCTORS(DownloadEventHandler);
};


// ----------------------------------------------------------------------------

// Checks that a url is safe.
class SafeBrowsingEventHandler
    : public ResourceDispatcherHost::EventHandler,
      public SafeBrowsingService::Client {
 public:
  SafeBrowsingEventHandler(ResourceDispatcherHost::EventHandler* handler,
                           int render_process_host_id,
                           int render_view_id,
                           const GURL& url,
                           ResourceType::Type resource_type,
                           SafeBrowsingService* safe_browsing,
                           ResourceDispatcherHost* resource_dispatcher_host)
      : next_handler_(handler),
        render_process_host_id_(render_process_host_id),
        render_view_id_(render_view_id),
        paused_request_id_(-1),
        safe_browsing_(safe_browsing),
        in_safe_browsing_check_(false),
        displaying_blocking_page_(false),
        rdh_(resource_dispatcher_host),
        queued_error_request_id_(-1),
        resource_type_(resource_type) {
    if (safe_browsing_->CheckUrl(url, this)) {
      safe_browsing_result_ = SafeBrowsingService::URL_SAFE;
      SafeBrowsingService::LogPauseDelay(TimeDelta());
    } else {
      AddRef();
      in_safe_browsing_check_ = true;
      // Can't pause now because it's too early, so we'll do it in OnWillRead.
    }
  }

  bool OnUploadProgress(int request_id, uint64 position, uint64 size) {
    return next_handler_->OnUploadProgress(request_id, position, size);
  }

  bool OnRequestRedirected(int request_id, const GURL& new_url) {
    if (in_safe_browsing_check_) {
      Release();
      in_safe_browsing_check_ = false;
      safe_browsing_->CancelCheck(this);
    }

    if (safe_browsing_->CheckUrl(new_url, this)) {
      safe_browsing_result_ = SafeBrowsingService::URL_SAFE;
      SafeBrowsingService::LogPauseDelay(TimeDelta());
    } else {
      AddRef();
      in_safe_browsing_check_ = true;
      // Can't pause now because it's too early, so we'll do it in OnWillRead.
    }

    return next_handler_->OnRequestRedirected(request_id, new_url);
  }

  bool OnResponseStarted(int request_id,
                         ResourceDispatcherHost::Response* response) {
    return next_handler_->OnResponseStarted(request_id, response);
  }

  void OnGetHashTimeout() {
    if (!in_safe_browsing_check_)
      return;

    safe_browsing_->CancelCheck(this);
    OnUrlCheckResult(GURL::EmptyGURL(), SafeBrowsingService::URL_SAFE);
  }

  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    if (in_safe_browsing_check_ && pause_time_.is_null()) {
      pause_time_ = Time::Now();
      MessageLoop::current()->PostDelayedTask(
          FROM_HERE,
          NewRunnableMethod(this, &SafeBrowsingEventHandler::OnGetHashTimeout),
          kMaxGetHashMs);
    }

    if (in_safe_browsing_check_ || displaying_blocking_page_) {
      rdh_->PauseRequest(render_process_host_id_, request_id, true);
      paused_request_id_ = request_id;
    }

    return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
  }

  bool OnReadCompleted(int request_id, int* bytes_read) {
    return next_handler_->OnReadCompleted(request_id, bytes_read);
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    if ((in_safe_browsing_check_ ||
         safe_browsing_result_ != SafeBrowsingService::URL_SAFE) &&
        status.status() == URLRequestStatus::FAILED &&
        status.os_error() == net::ERR_NAME_NOT_RESOLVED) {
      // Got a DNS error while the safebrowsing check is in progress or we
      // already know that the site is unsafe.  Don't show the the dns error
      // page.
      queued_error_.reset(new URLRequestStatus(status));
      queued_error_request_id_ = request_id;
      return true;
    }

    return next_handler_->OnResponseCompleted(request_id, status);
  }

  // SafeBrowsingService::Client implementation, called on the IO thread once
  // the URL has been classified.
  void OnUrlCheckResult(const GURL& url,
                        SafeBrowsingService::UrlCheckResult result) {
    DCHECK(in_safe_browsing_check_);
    DCHECK(!displaying_blocking_page_);

    safe_browsing_result_ = result;
    in_safe_browsing_check_ = false;

    if (result == SafeBrowsingService::URL_SAFE) {
      if (paused_request_id_ != -1) {
        rdh_->PauseRequest(render_process_host_id_, paused_request_id_, false);
        paused_request_id_ = -1;
      }

      TimeDelta pause_delta;
      if (!pause_time_.is_null())
        pause_delta = Time::Now() - pause_time_;
      SafeBrowsingService::LogPauseDelay(pause_delta);

      if (queued_error_.get()) {
        next_handler_->OnResponseCompleted(
            queued_error_request_id_, *queued_error_.get());
        queued_error_.reset();
      }

      Release();
    } else {
      displaying_blocking_page_ = true;
      safe_browsing_->DisplayBlockingPage(
          url, resource_type_, result, this, rdh_->ui_loop(),
          render_process_host_id_, render_view_id_);
    }
  }

  // SafeBrowsingService::Client implementation, called on the IO thread when
  // the user has decided to proceed with the current request, or go back.
  void OnBlockingPageComplete(bool proceed) {
    DCHECK(displaying_blocking_page_);
    displaying_blocking_page_ = false;

    if (proceed) {
      safe_browsing_result_ = SafeBrowsingService::URL_SAFE;
      if (paused_request_id_ != -1) {
        rdh_->PauseRequest(render_process_host_id_, paused_request_id_, false);
        paused_request_id_ = -1;
      }

      if (queued_error_.get()) {
        next_handler_->OnResponseCompleted(
            queued_error_request_id_, *queued_error_.get());
        queued_error_.reset();
      }
    } else {
      rdh_->CancelRequest(render_process_host_id_, paused_request_id_, false);
    }

    Release();
  }

 private:
  scoped_refptr<ResourceDispatcherHost::EventHandler> next_handler_;
  int render_process_host_id_;
  int render_view_id_;
  int paused_request_id_;  // -1 if not paused
  bool in_safe_browsing_check_;
  bool displaying_blocking_page_;
  SafeBrowsingService::UrlCheckResult safe_browsing_result_;
  scoped_refptr<SafeBrowsingService> safe_browsing_;
  scoped_ptr<URLRequestStatus> queued_error_;
  int queued_error_request_id_;
  ResourceDispatcherHost* rdh_;
  Time pause_time_;
  ResourceType::Type resource_type_;
};

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::CrossSiteEventHandler

// Task to notify the WebContents that a cross-site response has begun, so that
// WebContents can tell the old page to run its onunload handler.
class ResourceDispatcherHost::CrossSiteNotifyTabTask : public Task {
 public:
  CrossSiteNotifyTabTask(int render_process_host_id,
                         int render_view_id,
                         int request_id)
    : render_process_host_id_(render_process_host_id),
      render_view_id_(render_view_id),
      request_id_(request_id) {}

  void Run() {
    RenderViewHost* view =
        RenderViewHost::FromID(render_process_host_id_, render_view_id_);
    if (view) {
      view->OnCrossSiteResponse(render_process_host_id_, request_id_);
    } else {
      // The view couldn't be found.
      // TODO(creis): Should notify the IO thread to proceed anyway, using
      // ResourceDispatcherHost::OnClosePageACK.
    }
  }

 private:
  int render_process_host_id_;
  int render_view_id_;
  int request_id_;
};

// Ensures that cross-site responses are delayed until the onunload handler of
// the previous page is allowed to run.  This handler wraps an
// AsyncEventHandler, and it sits inside SafeBrowsing and Buffered event
// handlers.  This is important, so that it can intercept OnResponseStarted
// after we determine that a response is safe and not a download.
class ResourceDispatcherHost::CrossSiteEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  CrossSiteEventHandler(ResourceDispatcherHost::EventHandler* handler,
                        int render_process_host_id,
                        int render_view_id,
                        ResourceDispatcherHost* resource_dispatcher_host)
      : next_handler_(handler),
        render_process_host_id_(render_process_host_id),
        render_view_id_(render_view_id),
        has_started_response_(false),
        in_cross_site_transition_(false),
        request_id_(-1),
        completed_during_transition_(false),
        completed_status_(),
        response_(NULL),
        rdh_(resource_dispatcher_host) {}

  bool OnRequestRedirected(int request_id, const GURL& new_url) {
    // We should not have started the transition before being redirected.
    DCHECK(!in_cross_site_transition_);
    return next_handler_->OnRequestRedirected(request_id, new_url);
  }

  bool OnResponseStarted(int request_id,
                         ResourceDispatcherHost::Response* response) {
    // At this point, we know that the response is safe to send back to the
    // renderer: it is not a download, and it has passed the SSL and safe
    // browsing checks.
    // We should not have already started the transition before now.
    DCHECK(!in_cross_site_transition_);
    has_started_response_ = true;

    // Look up the request and associated info.
    GlobalRequestID global_id(render_process_host_id_, request_id);
    URLRequest* request = rdh_->GetURLRequest(global_id);
    if (!request) {
      DLOG(WARNING) << "Request wasn't found";
      return false;
    }
    ExtraRequestInfo* info = ExtraInfoForRequest(request);

    // If this is a download, just pass the response through without doing a
    // cross-site check.  The renderer will see it is a download and abort the
    // request.
    if (info->is_download) {
      return next_handler_->OnResponseStarted(request_id, response);
    }

    // Tell the renderer to run the onunload event handler, and wait for the
    // reply.
    StartCrossSiteTransition(request_id, response, global_id);
    return true;
  }

  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    return next_handler_->OnWillRead(request_id, buf, buf_size, min_size);
  }

  bool OnReadCompleted(int request_id, int* bytes_read) {
    if (!in_cross_site_transition_) {
      return next_handler_->OnReadCompleted(request_id, bytes_read);
    }
    return true;
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    if (!in_cross_site_transition_) {
      if (has_started_response_) {
        // We've already completed the transition, so just pass it through.
        return next_handler_->OnResponseCompleted(request_id, status);
      } else {
        // Some types of failures will call OnResponseCompleted without calling
        // CrossSiteEventHandler::OnResponseStarted.  We should wait now for
        // the cross-site transition.  Also continue with the logic below to
        // remember that we completed during the cross-site transition.
        GlobalRequestID global_id(render_process_host_id_, request_id);
        StartCrossSiteTransition(request_id, NULL, global_id);
      }
    }

    // We have to buffer the call until after the transition completes.
    completed_during_transition_ = true;
    completed_status_ = status;

    // Return false to tell RDH not to notify the world or clean up the
    // pending request.  We will do so in ResumeResponse.
    return false;
  }

  // We can now send the response to the new renderer, which will cause
  // WebContents to swap in the new renderer and destroy the old one.
  void ResumeResponse() {
    DCHECK(request_id_ != -1);
    DCHECK(in_cross_site_transition_);
    in_cross_site_transition_ = false;

    // Find the request for this response.
    GlobalRequestID global_id(render_process_host_id_, request_id_);
    URLRequest* request = rdh_->GetURLRequest(global_id);
    if (!request) {
      DLOG(WARNING) << "Resuming a request that wasn't found";
      return;
    }
    ExtraRequestInfo* info = ExtraInfoForRequest(request);

    if (has_started_response_) {
      // Send OnResponseStarted to the new renderer.
      DCHECK(response_);
      next_handler_->OnResponseStarted(request_id_, response_);

      // Unpause the request to resume reading.  Any further reads will be
      // directed toward the new renderer.
      rdh_->PauseRequest(render_process_host_id_, request_id_, false);
    }

    // Remove ourselves from the ExtraRequestInfo.
    info->cross_site_handler = NULL;

    // If the response completed during the transition, notify the next
    // event handler.
    if (completed_during_transition_) {
      next_handler_->OnResponseCompleted(request_id_, completed_status_);

      // Since we didn't notify the world or clean up the pending request in
      // RDH::OnResponseCompleted during the transition, we should do it now.
      rdh_->NotifyResponseCompleted(request, render_process_host_id_);
      rdh_->RemovePendingRequest(render_process_host_id_, request_id_);
    }
  }

 private:
  // Prepare to render the cross-site response in a new RenderViewHost, by
  // telling the old RenderViewHost to run its onunload handler.
  void StartCrossSiteTransition(int request_id,
                                ResourceDispatcherHost::Response* response,
                                GlobalRequestID global_id) {
    in_cross_site_transition_ = true;
    request_id_ = request_id;
    response_ = response;

    // Store this handler on the ExtraRequestInfo, so that RDH can call our
    // ResumeResponse method when the close ACK is received.
    URLRequest* request = rdh_->GetURLRequest(global_id);
    if (!request) {
      DLOG(WARNING) << "Cross site response for a request that wasn't found";
      return;
    }
    ExtraRequestInfo* info = ExtraInfoForRequest(request);
    info->cross_site_handler = this;

    if (has_started_response_) {
      // Pause the request until the old renderer is finished and the new
      // renderer is ready.
      rdh_->PauseRequest(render_process_host_id_, request_id, true);
    }
    // If our OnResponseStarted wasn't called, then we're being called by
    // OnResponseCompleted after a failure.  We don't need to pause, because
    // there will be no reads.

    // Tell the tab responsible for this request that a cross-site response is
    // starting, so that it can tell its old renderer to run its onunload
    // handler now.  We will wait to hear the corresponding ClosePage_ACK.
    ResourceDispatcherHost::CrossSiteNotifyTabTask* task =
        new CrossSiteNotifyTabTask(render_process_host_id_,
                                   render_view_id_,
                                   request_id);
    rdh_->ui_loop()->PostTask(FROM_HERE, task);
  }

  scoped_refptr<ResourceDispatcherHost::EventHandler> next_handler_;
  int render_process_host_id_;
  int render_view_id_;
  bool has_started_response_;
  bool in_cross_site_transition_;
  int request_id_;
  bool completed_during_transition_;
  URLRequestStatus completed_status_;
  ResourceDispatcherHost::Response* response_;
  ResourceDispatcherHost* rdh_;
};

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::BufferedEventHandler

// Used to buffer a request until enough data has been received.
class ResourceDispatcherHost::BufferedEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  BufferedEventHandler(ResourceDispatcherHost::EventHandler* handler,
                       ResourceDispatcherHost* host, URLRequest* request)
    : real_handler_(handler),
      host_(host),
      request_(request),
      bytes_read_(0),
      sniff_content_(false),
      should_buffer_(false),
      buffering_(false),
      finished_(false) {}

  bool OnUploadProgress(int request_id, uint64 position, uint64 size) {
    return real_handler_->OnUploadProgress(request_id, position, size);
  }

  bool OnRequestRedirected(int request_id, const GURL& new_url) {
    return real_handler_->OnRequestRedirected(request_id, new_url);
  }

  bool OnResponseStarted(int request_id, Response* response) {
    response_ = response;
    if (!DelayResponse())
      return CompleteResponseStarted(request_id);
    return true;
  }

  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size);
  bool OnReadCompleted(int request_id, int* bytes_read);

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    return real_handler_->OnResponseCompleted(request_id, status);
  }

 private:
  // Returns true if we should delay OnResponseStarted forwarding.
  bool DelayResponse();

  // Returns true if there will be a need to parse the DocType of the document
  // to determine the right way to handle it.
  bool ShouldBuffer(const GURL& url, const std::string& mime_type);

  // Returns true if there is enough information to process the DocType.
  bool DidBufferEnough(int bytes_read) {
    const int kRequiredLength = 256;

    return bytes_read >= kRequiredLength;
  }

  // Returns true if we have to keep buffering data.
  bool KeepBuffering(int bytes_read);

  // Sends a pending OnResponseStarted notification.
  bool CompleteResponseStarted(int request_id);

  scoped_refptr<ResourceDispatcherHost::EventHandler> real_handler_;
  scoped_refptr<Response> response_;
  ResourceDispatcherHost* host_;
  URLRequest* request_;
  char* read_buffer_;
  int read_buffer_size_;
  int bytes_read_;
  bool sniff_content_;
  bool should_buffer_;
  bool buffering_;
  bool finished_;

  DISALLOW_EVIL_CONSTRUCTORS(BufferedEventHandler);
};

// We'll let the original event handler provide a buffer, and reuse it for
// subsequent reads until we're done buffering.
bool ResourceDispatcherHost::BufferedEventHandler::OnWillRead(
    int request_id, char** buf, int* buf_size, int min_size) {
  if (buffering_) {
    *buf = read_buffer_ + bytes_read_;
    *buf_size = read_buffer_size_ - bytes_read_;
    DCHECK(*buf_size > 0);
    return true;
  }

  if (finished_)
    return false;

  bool ret = real_handler_->OnWillRead(request_id, buf, buf_size, min_size);
  read_buffer_ = *buf;
  read_buffer_size_ = *buf_size;
  bytes_read_ = 0;
  return ret;
}

bool ResourceDispatcherHost::BufferedEventHandler::OnReadCompleted(
    int request_id, int* bytes_read) {
  ResourceDispatcherHost::ExtraRequestInfo* info =
      ResourceDispatcherHost::ExtraInfoForRequest(request_);

  if (sniff_content_ || should_buffer_) {
    if (KeepBuffering(*bytes_read))
      return true;

    LOG(INFO) << "Finished buffering " << request_->url().spec();
    sniff_content_ = should_buffer_ = false;
    *bytes_read = bytes_read_;

    // Done buffering, send the pending ResponseStarted event.
    if (!CompleteResponseStarted(request_id))
      return false;
  }

  return real_handler_->OnReadCompleted(request_id, bytes_read);
}

bool ResourceDispatcherHost::BufferedEventHandler::DelayResponse() {
  std::string mime_type;
  request_->GetMimeType(&mime_type);

  if (net::ShouldSniffMimeType(request_->url(), mime_type)) {
    // We're going to look at the data before deciding what the content type
    // is.  That means we need to delay sending the ResponseStarted message
    // over the IPC channel.
    sniff_content_ = true;
    LOG(INFO) << "To buffer: " << request_->url().spec();
    return true;
  }

  if (ShouldBuffer(request_->url(), mime_type)) {
    // This is a temporary fix for the fact that webkit expects to have
    // enough data to decode the doctype in order to select the rendering
    // mode.
    should_buffer_ = true;
    LOG(INFO) << "To buffer: " << request_->url().spec();
    return true;
  }
  return false;
}

bool ResourceDispatcherHost::BufferedEventHandler::ShouldBuffer(
    const GURL& url, const std::string& mime_type) {
  // We are willing to buffer for HTTP and HTTPS.
  bool sniffable_scheme = url.is_empty() ||
                          url.SchemeIs("http") ||
                          url.SchemeIs("https");
  if (!sniffable_scheme)
    return false;

  // Today, the only reason to buffer the request is to fix the doctype decoding
  // performed by webkit: if there is not enough data it will go to quirks mode.
  // We only expect the doctype check to apply to html documents.
  return mime_type == "text/html";
}

bool ResourceDispatcherHost::BufferedEventHandler::KeepBuffering(
    int bytes_read) {
  DCHECK(read_buffer_);
  bytes_read_ += bytes_read;
  finished_ = (bytes_read == 0);

  if (sniff_content_) {
    std::string type_hint, new_type;
    request_->GetMimeType(&type_hint);

    if (!net::SniffMimeType(read_buffer_, bytes_read_, request_->url(),
                            type_hint, &new_type)) {
      // SniffMimeType() returns false if there is not enough data to determine
      // the mime type. However, even if it returns false, it returns a new type
      // that is probably better than the current one.
      if (!finished_) {
        buffering_ = true;
        return true;
      }
    }
    sniff_content_ = false;
    response_->response_head.mime_type.assign(new_type);

    // We just sniffed the mime type, maybe there is a doctype to process.
    if (ShouldBuffer(request_->url(), new_type))
      should_buffer_ = true;
  }

  if (!finished_ && should_buffer_) {
    if (!DidBufferEnough(bytes_read_)) {
      buffering_ = true;
      return true;
    }
  }
  buffering_ = false;
  return false;
}

bool ResourceDispatcherHost::BufferedEventHandler::CompleteResponseStarted(
    int request_id) {
  // Check to see if we should forward the data from this request to the
  // download thread.
  // TODO(paulg): Only download if the context from the renderer allows it.
  std::string content_disposition;
  request_->GetResponseHeaderByName("content-disposition",
                                    &content_disposition);

  ResourceDispatcherHost::ExtraRequestInfo* info =
      ResourceDispatcherHost::ExtraInfoForRequest(request_);

  if (info->allow_download &&
      host_->ShouldDownload(response_->response_head.mime_type,
                            content_disposition)) {
    if (response_->response_head.headers &&  // Can be NULL if FTP.
        response_->response_head.headers->response_code() / 100 != 2) {
      // The response code indicates that this is an error page, but we don't
      // know how to display the content.  We follow Firefox here and show our
      // own error page instead of triggering a download.
      // TODO(abarth): We should abstract the response_code test, but this kind
      //               of check is scattered throughout our codebase.
      request_->CancelWithError(net::ERR_FILE_NOT_FOUND);
      return false;
    }

    info->is_download = true;

    scoped_refptr<DownloadEventHandler> download_handler =
        new DownloadEventHandler(host_,
                                 info->render_process_host_id,
                                 info->render_view_id,
                                 request_id,
                                 request_->url().spec(),
                                 host_->download_file_manager(),
                                 request_, false);
    if (bytes_read_) {
      // a Read has already occurred and we need to copy the data into the
      // DownloadEventHandler.
      char *buf;
      int buf_len;
      download_handler->OnWillRead(request_id, &buf, &buf_len, bytes_read_);
      DCHECK(buf_len >= bytes_read_);
      memcpy(buf, read_buffer_, bytes_read_);
    }
    // Update the renderer with the response headers which will cause it to
    // cancel the request.
    // TODO(paulg): Send the renderer a response that indicates that the request
    //              will be handled by an external source (the browser).
    real_handler_->OnResponseStarted(info->request_id, response_);
    real_handler_ = download_handler;
  }
  return real_handler_->OnResponseStarted(request_id, response_);
}

namespace {

// Consults the RendererSecurity policy to determine whether the
// ResourceDispatcherHost should service this request.  A request might be
// disallowed if the renderer is not authorized to restrive the request URL or
// if the renderer is attempting to upload an unauthorized file.
bool ShouldServiceRequest(int render_process_host_id,
                          const ViewHostMsg_Resource_Request& request_data)  {
  // TODO(mpcomplete): remove this when http://b/viewIssue?id=1080959 is fixed.
  if (render_process_host_id == -1)
    return true;

  RendererSecurityPolicy* policy = RendererSecurityPolicy::GetInstance();

  // Check if the renderer is permitted to request the requested URL.
  if (!policy->CanRequestURL(render_process_host_id, request_data.url)) {
    LOG(INFO) << "Denied unauthorized request for " <<
        request_data.url.possibly_invalid_spec();
    return false;
  }

  // Check if the renderer is permitted to upload the requested files.
  const std::vector<net::UploadData::Element>& uploads =
      request_data.upload_content;
  for (std::vector<net::UploadData::Element>::const_iterator iter(uploads.begin());
      iter != uploads.end(); ++iter) {
    if (iter->type() == net::UploadData::TYPE_FILE &&
        !policy->CanUploadFile(render_process_host_id, iter->file_path())) {
      NOTREACHED() << "Denied unauthorized upload of " << iter->file_path();
      return false;
    }
  }

  return true;
}

}  // namespace

// ----------------------------------------------------------------------------
// ResourceDispatcherHost::SaveFileEventHandler
// Forwards data to the save thread.
class ResourceDispatcherHost::SaveFileEventHandler
    : public ResourceDispatcherHost::EventHandler {
 public:
  SaveFileEventHandler(int render_process_host_id,
                       int render_view_id,
                       const std::string& url,
                       SaveFileManager* manager)
      : save_id_(-1),
        render_process_id_(render_process_host_id),
        render_view_id_(render_view_id),
        read_buffer_(NULL),
        url_(UTF8ToWide(url)),
        content_length_(0),
        save_manager_(manager) {
  }

  // Save the redirected URL to final_url_, we need to use the original
  // URL to match original request.
  bool OnRequestRedirected(int request_id, const GURL& url) {
    final_url_ = UTF8ToWide(url.spec());
    return true;
  }

  // Send the download creation information to the download thread.
  bool OnResponseStarted(int request_id, Response* response) {
    save_id_ = save_manager_->GetNextId();
    // |save_manager_| consumes (deletes):
    SaveFileCreateInfo* info = new SaveFileCreateInfo;
    info->url = url_;
    info->final_url = final_url_;
    info->total_bytes = content_length_;
    info->save_id = save_id_;
    info->render_process_id = render_process_id_;
    info->render_view_id = render_view_id_;
    info->request_id = request_id;
    info->content_disposition = content_disposition_;
    info->save_source = SaveFileCreateInfo::SAVE_FILE_FROM_NET;
    save_manager_->GetSaveLoop()->PostTask(FROM_HERE,
        NewRunnableMethod(save_manager_,
                          &SaveFileManager::StartSave,
                          info));
    return true;
  }

  // Create a new buffer, which will be handed to the download thread for file
  // writing and deletion.
  bool OnWillRead(int request_id, char** buf, int* buf_size, int min_size) {
    DCHECK(buf && buf_size);
    if (!read_buffer_) {
      *buf_size = min_size < 0 ? kReadBufSize : min_size;
      read_buffer_ = new char[*buf_size];
    }
    *buf = read_buffer_;
    return true;
  }

  // Pass the buffer to the download file writer.
  bool OnReadCompleted(int request_id, int* bytes_read) {
    DCHECK(read_buffer_);
    save_manager_->GetSaveLoop()->PostTask(FROM_HERE,
        NewRunnableMethod(save_manager_,
                          &SaveFileManager::UpdateSaveProgress,
                          save_id_,
                          read_buffer_,
                          *bytes_read));
    read_buffer_ = NULL;
    return true;
  }

  bool OnResponseCompleted(int request_id, const URLRequestStatus& status) {
    save_manager_->GetSaveLoop()->PostTask(FROM_HERE,
        NewRunnableMethod(save_manager_,
                          &SaveFileManager::SaveFinished,
                          save_id_,
                          url_,
                          render_process_id_,
                          status.is_success() && !status.is_io_pending()));
    delete [] read_buffer_;
    return true;
  }

  // If the content-length header is not present (or contains something other
  // than numbers), StringToInt64 returns 0, which indicates 'unknown size' and
  // is handled correctly by the SaveManager.
  void set_content_length(const std::string& content_length) {
    content_length_ = StringToInt64(content_length);
  }

  void set_content_disposition(const std::string& content_disposition) {
    content_disposition_ = content_disposition;
  }

 private:
  int save_id_;
  int render_process_id_;
  int render_view_id_;
  char* read_buffer_;
  std::string content_disposition_;
  std::wstring url_;
  std::wstring final_url_;
  int64 content_length_;
  SaveFileManager* save_manager_;

  static const int kReadBufSize = 32768;  // bytes

  DISALLOW_EVIL_CONSTRUCTORS(SaveFileEventHandler);
};

// ----------------------------------------------------------------------------
// ResourceDispatcherHost

ResourceDispatcherHost::ResourceDispatcherHost(MessageLoop* io_loop)
    : ui_loop_(MessageLoop::current()),
      io_loop_(io_loop),
      update_load_states_timer_(
          TimeDelta::FromMilliseconds(kUpdateLoadStatesIntervalMsec)),
      download_file_manager_(new DownloadFileManager(ui_loop_, this)),
      save_file_manager_(new SaveFileManager(ui_loop_, io_loop, this)),
      safe_browsing_(new SafeBrowsingService),
      request_id_(-1),
      plugin_service_(PluginService::GetInstance()),
      method_runner_(this),
      is_shutdown_(false) {
  update_load_states_timer_.set_task(method_runner_.NewRunnableMethod(
      &ResourceDispatcherHost::UpdateLoadStates));
}

ResourceDispatcherHost::~ResourceDispatcherHost() {
  AsyncEventHandler::GlobalCleanup();
  STLDeleteValues(&pending_requests_);
}

void ResourceDispatcherHost::Initialize() {
  DCHECK(MessageLoop::current() == ui_loop_);
  download_file_manager_->Initialize();
  safe_browsing_->Initialize(io_loop_);
}

// A ShutdownTask proxies a shutdown task from the UI thread to the IO thread.
// It should be constructed on the UI thread and run in the IO thread.
class ResourceDispatcherHost::ShutdownTask : public Task {
 public:
  explicit ShutdownTask(ResourceDispatcherHost* resource_dispatcher_host)
      : rdh_(resource_dispatcher_host) { }

  void Run() {
    rdh_->OnShutdown();
  }

 private:
  ResourceDispatcherHost* rdh_;
};

void ResourceDispatcherHost::Shutdown() {
  DCHECK(MessageLoop::current() == ui_loop_);
  io_loop_->PostTask(FROM_HERE, new ShutdownTask(this));
}

void ResourceDispatcherHost::OnShutdown() {
  DCHECK(MessageLoop::current() == io_loop_);
  is_shutdown_ = true;
  STLDeleteValues(&pending_requests_);
}

bool ResourceDispatcherHost::HandleExternalProtocol(int request_id,
                                                    int render_process_host_id,
                                                    int tab_contents_id,
                                                    const GURL& url,
                                                    ResourceType::Type type,
                                                    EventHandler* handler) {
  if (!ResourceType::IsFrame(type) || URLRequest::IsHandledURL(url))
    return false;

  ui_loop_->PostTask(FROM_HERE, NewRunnableFunction(
      &ExternalProtocolHandler::LaunchUrl, url, render_process_host_id,
      tab_contents_id));

  handler->OnResponseCompleted(request_id, URLRequestStatus(
                                               URLRequestStatus::FAILED,
                                               net::ERR_ABORTED));
  return true;
}

void ResourceDispatcherHost::BeginRequest(
    Receiver* receiver,
    HANDLE render_process_handle,
    int render_process_host_id,
    int render_view_id,
    int request_id,
    const ViewHostMsg_Resource_Request& request_data,
    URLRequestContext* request_context,
    IPC::Message* sync_result) {
  if (is_shutdown_ ||
      !ShouldServiceRequest(render_process_host_id, request_data)) {
    // Tell the renderer that this request was disallowed.
    receiver->Send(new ViewMsg_Resource_RequestComplete(
        render_view_id,
        request_id,
        URLRequestStatus(URLRequestStatus::FAILED, net::ERR_ABORTED)));
    return;
  }

  // Ensure the Chrome plugins are loaded, as they may intercept network
  // requests.  Does nothing if they are already loaded.
  // TODO(mpcomplete): This takes 200 ms!  Investigate parallelizing this by
  // starting the load earlier in a BG thread.
  plugin_service_->LoadChromePlugins(this);

  // Construct the event handler.
  scoped_refptr<EventHandler> handler;
  if (sync_result) {
    handler = new SyncEventHandler(receiver, request_data.url, sync_result);
  } else {
    handler = new AsyncEventHandler(receiver,
                                    render_process_host_id,
                                    render_view_id,
                                    render_process_handle,
                                    request_data.url,
                                    this);
  }

  if (HandleExternalProtocol(request_id, render_process_host_id, render_view_id,
                             request_data.url, request_data.resource_type,
                             handler)) {
    return;
  }

  // Construct the request.
  URLRequest* request = new URLRequest(request_data.url, this);
  request->set_method(request_data.method);
  request->set_policy_url(request_data.policy_url);
  request->set_referrer(request_data.referrer.spec());
  request->SetExtraRequestHeaders(request_data.headers);
  request->set_load_flags(request_data.load_flags);
  request->set_enable_profiling(g_navigation_profiler.is_profiling());
  request->set_context(request_context);
  request->set_origin_pid(request_data.origin_pid);

  // Set upload data.
  uint64 upload_size = 0;
  if (!request_data.upload_content.empty()) {
    scoped_refptr<net::UploadData> upload = new net::UploadData();
    upload->set_elements(request_data.upload_content);  // Deep copy.
    request->set_upload(upload);
    upload_size = upload->GetContentLength();
  }

  // Install a CrossSiteEventHandler if this request is coming from a
  // RenderViewHost with a pending cross-site request.  We only check this for
  // MAIN_FRAME requests.
  // TODO(mpcomplete): remove "render_process_host_id != -1"
  //                   when http://b/viewIssue?id=1080959 is fixed.
  if (request_data.resource_type == ResourceType::MAIN_FRAME &&
      render_process_host_id != -1 &&
      Singleton<CrossSiteRequestManager>::get()->
          HasPendingCrossSiteRequest(render_process_host_id, render_view_id)) {
    // Wrap the event handler to be sure the current page's onunload handler
    // has a chance to run before we render the new page.
    handler = new CrossSiteEventHandler(handler,
                                        render_process_host_id,
                                        render_view_id,
                                        this);
  }

  if (safe_browsing_->enabled() &&
      safe_browsing_->CanCheckUrl(request_data.url)) {
    handler = new SafeBrowsingEventHandler(handler,
                                           render_process_host_id,
                                           render_view_id,
                                           request_data.url,
                                           request_data.resource_type,
                                           safe_browsing_,
                                           this);
  }

  // Insert a buffered event handler before the actual one.
  handler = new BufferedEventHandler(handler, this, request);

  // Make extra info and read footer (contains request ID).
  ExtraRequestInfo* extra_info =
      new ExtraRequestInfo(handler,
                           request_id,
                           render_process_host_id,
                           render_view_id,
                           request_data.mixed_content,
                           request_data.resource_type,
                           upload_size);
  extra_info->allow_download =
      ResourceType::IsFrame(request_data.resource_type);
  request->set_user_data(extra_info);  // takes pointer ownership

  BeginRequestInternal(request, request_data.mixed_content);
}

// We are explicitly forcing the download of 'url'.
void ResourceDispatcherHost::BeginDownload(const GURL& url,
                                           const GURL& referrer,
                                           int render_process_host_id,
                                           int render_view_id,
                                           URLRequestContext* request_context) {
  if (is_shutdown_)
    return;

  // Check if the renderer is permitted to request the requested URL.
  //
  // TODO(mpcomplete): remove "render_process_host_id != -1"
  //                   when http://b/viewIssue?id=1080959 is fixed.
  if (render_process_host_id != -1 &&
      !RendererSecurityPolicy::GetInstance()->
          CanRequestURL(render_process_host_id, url)) {
    LOG(INFO) << "Denied unauthorized download request for " <<
        url.possibly_invalid_spec();
    return;
  }

  // Ensure the Chrome plugins are loaded, as they may intercept network
  // requests.  Does nothing if they are already loaded.
  plugin_service_->LoadChromePlugins(this);
  URLRequest* request = new URLRequest(url, this);

  request_id_--;

  scoped_refptr<EventHandler> handler =
      new DownloadEventHandler(this,
                               render_process_host_id,
                               render_view_id,
                               request_id_,
                               url.spec(),
                               download_file_manager_.get(),
                               request,
                               true);


  if (safe_browsing_->enabled() && safe_browsing_->CanCheckUrl(url)) {
    handler = new SafeBrowsingEventHandler(handler,
                                           render_process_host_id,
                                           render_view_id,
                                           url,
                                           ResourceType::MAIN_FRAME,
                                           safe_browsing_,
                                           this);
  }

  bool known_proto = URLRequest::IsHandledURL(url);
  if (!known_proto) {
    CHECK(false);
  }

  request->set_method("GET");
  request->set_referrer(referrer.spec());
  request->set_enable_profiling(g_navigation_profiler.is_profiling());
  request->set_context(request_context);

  ExtraRequestInfo* extra_info =
      new ExtraRequestInfo(handler,
                           request_id_,
                           render_process_host_id,
                           render_view_id,
                           false,  // Downloads are not considered mixed-content
                           ResourceType::SUB_RESOURCE,
                           0 /* upload_size */ );
  extra_info->allow_download = true;
  extra_info->is_download = true;
  request->set_user_data(extra_info);  // Takes pointer ownership.

  BeginRequestInternal(request, false);
}

// This function is only used for saving feature.
void ResourceDispatcherHost::BeginSaveFile(const GURL& url,
                                           const GURL& referrer,
                                           int render_process_host_id,
                                           int render_view_id,
                                           URLRequestContext* request_context) {
  if (is_shutdown_)
    return;

  // Ensure the Chrome plugins are loaded, as they may intercept network
  // requests.  Does nothing if they are already loaded.
  plugin_service_->LoadChromePlugins(this);

  scoped_refptr<EventHandler> handler =
      new SaveFileEventHandler(render_process_host_id,
                               render_view_id,
                               url.spec(),
                               save_file_manager_.get());
  request_id_--;

  bool known_proto = URLRequest::IsHandledURL(url);
  if (!known_proto) {
    // Since any URLs which have non-standard scheme have been filtered
    // by save manager(see GURL::SchemeIsStandard). This situation
    // should not happen.
    NOTREACHED();
    return;
  }

  URLRequest* request = new URLRequest(url, this);
  request->set_method("GET");
  request->set_referrer(referrer.spec());
  request->set_enable_profiling(g_navigation_profiler.is_profiling());
  // So far, for saving page, we need fetch content from cache, in the
  // future, maybe we can use a configuration to configure this behavior.
  request->set_load_flags(net::LOAD_ONLY_FROM_CACHE);
  request->set_context(request_context);

  ExtraRequestInfo* extra_info =
      new ExtraRequestInfo(handler,
                           request_id_,
                           render_process_host_id,
                           render_view_id,
                           false,
                           ResourceType::SUB_RESOURCE,
                           0 /* upload_size */);
  // Just saving some resources we need, disallow downloading.
  extra_info->allow_download = false;
  extra_info->is_download = false;
  request->set_user_data(extra_info);  // Takes pointer ownership.

  BeginRequestInternal(request, false);
}

void ResourceDispatcherHost::CancelRequest(int render_process_host_id,
                                           int request_id,
                                           bool from_renderer) {
  PendingRequestList::iterator i = pending_requests_.find(
      GlobalRequestID(render_process_host_id, request_id));
  if (i == pending_requests_.end()) {
    // We probably want to remove this warning eventually, but I wanted to be
    // able to notice when this happens during initial development since it
    // should be rare and may indicate a bug.
    DLOG(WARNING) << "Canceling a request that wasn't found";
    return;
  }

  // WebKit will send us a cancel for downloads since it no longer handles them.
  // In this case, ignore the cancel since we handle downloads in the browser.
  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);
  if (!from_renderer || !info->is_download) {
    if (info->login_handler) {
      info->login_handler->OnRequestCancelled();
      info->login_handler = NULL;
    }
    i->second->Cancel();
  }

  // Do not remove from the pending requests, as the request will still
  // call AllDataReceived, and may even have more data before it does
  // that.
}

void ResourceDispatcherHost::OnDataReceivedACK(int render_process_host_id,
                                               int request_id) {
  PendingRequestList::iterator i = pending_requests_.find(
      GlobalRequestID(render_process_host_id, request_id));
  if (i == pending_requests_.end())
    return;

  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);

  // Decrement the number of pending data messages.
  info->pending_data_count--;

  // If the pending data count was higher than the max, resume the request.
  if (info->pending_data_count == kMaxPendingDataMessages) {
    // Decrement the pending data count one more time because we also
    // incremented it before pausing the request.
    info->pending_data_count--;

    // Resume the request.
    PauseRequest(render_process_host_id, request_id, false);
  }
}

void ResourceDispatcherHost::OnUploadProgressACK(int render_process_host_id,
                                                 int request_id) {
  PendingRequestList::iterator i = pending_requests_.find(
      GlobalRequestID(render_process_host_id, request_id));
  if (i == pending_requests_.end())
    return;

  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);
  info->waiting_for_upload_progress_ack = false;
}

bool ResourceDispatcherHost::WillSendData(int render_process_host_id,
                                          int request_id) {
  PendingRequestList::iterator i = pending_requests_.find(
      GlobalRequestID(render_process_host_id, request_id));
  if (i == pending_requests_.end()) {
    NOTREACHED() << L"WillSendData for invalid request";
    return false;
  }

  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);

  info->pending_data_count++;
  if (info->pending_data_count > kMaxPendingDataMessages) {
    // We reached the max number of data messages that can be sent to
    // the renderer for a given request. Pause the request and wait for
    // the renderer to start processing them before resuming it.
    PauseRequest(render_process_host_id, request_id, true);
    return false;
  }

  return true;
}

void ResourceDispatcherHost::PauseRequest(int render_process_host_id,
                                          int request_id,
                                          bool pause) {
  GlobalRequestID global_id(render_process_host_id, request_id);
  PendingRequestList::iterator i = pending_requests_.find(global_id);
  if (i == pending_requests_.end()) {
    DLOG(WARNING) << "Pausing a request that wasn't found";
    return;
  }

  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);

  int pause_count = info->pause_count + (pause ? 1 : -1);
  if (pause_count < 0) {
    NOTREACHED();  // Unbalanced call to pause.
    return;
  }
  info->pause_count = pause_count;

  RESOURCE_LOG("To pause (" << pause << "): " << i->second->url().spec());

  // If we're resuming, kick the request to start reading again. Run the read
  // asynchronously to avoid recursion problems.
  if (info->pause_count == 0) {
    MessageLoop::current()->PostTask(FROM_HERE,
        method_runner_.NewRunnableMethod(
            &ResourceDispatcherHost::ResumeRequest, global_id));
  }
}

void ResourceDispatcherHost::OnClosePageACK(int render_process_host_id,
                                            int request_id,
                                            bool is_closing_browser) {
  GlobalRequestID global_id(render_process_host_id, request_id);
  PendingRequestList::iterator i = pending_requests_.find(global_id);
  if (i == pending_requests_.end()) {
    // If there are no matching pending requests, then this is not a
    // cross-site navigation and we are just closing the tab/browser.
    ui_loop_->PostTask(FROM_HERE, NewRunnableFunction(
        &RenderViewHost::ClosePageIgnoringUnloadEvents,
        render_process_host_id,
        request_id,
        is_closing_browser));
    return;
  }

  ExtraRequestInfo* info = ExtraInfoForRequest(i->second);
  if (info->cross_site_handler) {
    info->cross_site_handler->ResumeResponse();
  }
}

// The object died, so cancel and detach all requests associated with it except
// for downloads, which belong to the browser process even if initiated via a
// renderer.
void ResourceDispatcherHost::CancelRequestsForProcess(
    int render_process_host_id) {
  CancelRequestsForRenderView(render_process_host_id, -1 /* cancel all */);
}

void ResourceDispatcherHost::CancelRequestsForRenderView(
    int render_process_host_id,
    int render_view_id) {
  // Since pending_requests_ is a map, we first build up a list of all of the
  // matching requests to be cancelled, and then we cancel them.  Since there
  // may be more than one request to cancel, we cannot simply hold onto the map
  // iterators found in the first loop.

  // Find the global ID of all matching elements.
  std::vector<GlobalRequestID> matching_requests;
  for (PendingRequestList::const_iterator i = pending_requests_.begin();
       i != pending_requests_.end(); ++i) {
    if (i->first.render_process_host_id == render_process_host_id) {
      ExtraRequestInfo* info = ExtraInfoForRequest(i->second);
      if (!info->is_download && (render_view_id == -1 ||
                                 render_view_id == info->render_view_id)) {
        matching_requests.push_back(
            GlobalRequestID(render_process_host_id, i->first.request_id));
      }
    }
  }

  // Remove matches.
  for (size_t i = 0; i < matching_requests.size(); ++i) {
    PendingRequestList::iterator iter =
        pending_requests_.find(matching_requests[i]);
    DCHECK(iter != pending_requests_.end());
    RemovePendingRequest(iter);
  }
}

// Cancels the request and removes it from the list.
void ResourceDispatcherHost::RemovePendingRequest(int render_process_host_id,
                                                  int request_id) {
  PendingRequestList::iterator i = pending_requests_.find(
      GlobalRequestID(render_process_host_id, request_id));
  if (i == pending_requests_.end()) {
    NOTREACHED() << "Trying to remove a request that's not here";
    return;
  }
  RemovePendingRequest(i);
}

void ResourceDispatcherHost::RemovePendingRequest(
    PendingRequestList::iterator& iter) {
  // Notify the login handler that this request object is going away.
  ExtraRequestInfo* info = ExtraInfoForRequest(iter->second);
  if (info && info->login_handler)
    info->login_handler->OnRequestCancelled();

  delete iter->second;
  pending_requests_.erase(iter);

  // If we have no more pending requests, then stop the load state monitor
  if (pending_requests_.empty())
    update_load_states_timer_.Stop();
}

// URLRequest::Delegate -------------------------------------------------------

void ResourceDispatcherHost::OnReceivedRedirect(URLRequest* request,
                                                const GURL& new_url) {
  RESOURCE_LOG("OnReceivedRedirect: " << request->url().spec());
  ExtraRequestInfo* info = ExtraInfoForRequest(request);

  DCHECK(request->status().is_success());

  // TODO(mpcomplete): remove this when http://b/viewIssue?id=1080959 is fixed.
  if (info->render_process_host_id != -1 &&
      !RendererSecurityPolicy::GetInstance()->
          CanRequestURL(info->render_process_host_id, new_url)) {
    LOG(INFO) << "Denied unauthorized request for " <<
        new_url.possibly_invalid_spec();

    // Tell the renderer that this request was disallowed.
    CancelRequest(info->render_process_host_id, info->request_id, false);
    return;
  }

  NofityReceivedRedirect(request, info->render_process_host_id, new_url);

  if (HandleExternalProtocol(info->request_id, info->render_process_host_id,
                             info->render_view_id, new_url,
                             info->resource_type, info->event_handler)) {
    // The request is complete so we can remove it.
    RemovePendingRequest(info->render_process_host_id, info->request_id);
    return;
  }

  if (!info->event_handler->OnRequestRedirected(info->request_id, new_url))
    CancelRequest(info->render_process_host_id, info->request_id, false);
}

void ResourceDispatcherHost::OnAuthRequired(
    URLRequest* request,
    net::AuthChallengeInfo* auth_info) {
  // Create a login dialog on the UI thread to get authentication data,
  // or pull from cache and continue on the IO thread.
  // TODO(mpcomplete): We should block the parent tab while waiting for
  // authentication.
  // That would also solve the problem of the URLRequest being cancelled
  // before we receive authentication.
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  DCHECK(!info->login_handler) <<
      "OnAuthRequired called with login_handler pending";
  info->login_handler = CreateLoginPrompt(auth_info, request, ui_loop_);
}

void ResourceDispatcherHost::OnSSLCertificateError(
    URLRequest* request,
    int cert_error,
    X509Certificate* cert) {
  DCHECK(request);
  SSLManager::OnSSLCertificateError(this, request, cert_error, cert, ui_loop_);
}

void ResourceDispatcherHost::OnResponseStarted(URLRequest* request) {
  RESOURCE_LOG("OnResponseStarted: " << request->url().spec());
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  if (PauseRequestIfNeeded(info)) {
    RESOURCE_LOG("OnResponseStarted pausing: " << request->url().spec());
    return;
  }

  if (request->status().is_success()) {
    // We want to send a final upload progress message prior to sending
    // the response complete message even if we're waiting for an ack to
    // to a previous upload progress message.
    info->waiting_for_upload_progress_ack = false;
    MaybeUpdateUploadProgress(info, request);

    if (!CompleteResponseStarted(request)) {
      CancelRequest(info->render_process_host_id, info->request_id, false);
    } else {
      // Start reading.
      int bytes_read = 0;
      if (Read(request, &bytes_read)) {
        OnReadCompleted(request, bytes_read);
      } else if (!request->status().is_io_pending()) {
        DCHECK(!info->is_paused);
        // If the error is not an IO pending, then we're done reading.
        OnResponseCompleted(request);
      }
    }
  } else {
    OnResponseCompleted(request);
  }
}

bool ResourceDispatcherHost::CompleteResponseStarted(URLRequest* request) {
  ExtraRequestInfo* info = ExtraInfoForRequest(request);

  scoped_refptr<Response> response(new Response);

  response->response_head.status = request->status();
  response->response_head.request_time = request->request_time();
  response->response_head.response_time = request->response_time();
  response->response_head.headers = request->response_headers();
  request->GetCharset(&response->response_head.charset);
  response->response_head.filter_policy = info->filter_policy;
  response->response_head.content_length = request->GetExpectedContentSize();
  request->GetMimeType(&response->response_head.mime_type);

  if (request->ssl_info().cert) {
    int cert_id =
        CertStore::GetSharedInstance()->StoreCert(
            request->ssl_info().cert,
            info->render_process_host_id);
    int cert_status = request->ssl_info().cert_status;
    // EV certificate verification could be expensive.  We don't want to spend
    // time performing EV certificate verification on all resources because
    // EV status is irrelevant to sub-frames and sub-resources.  So we call
    // IsEV here rather than in the network layer because the network layer
    // doesn't know the resource type.
    if (info->resource_type == ResourceType::MAIN_FRAME &&
        request->ssl_info().cert->IsEV(cert_status))
      cert_status |= net::CERT_STATUS_IS_EV;

    response->response_head.security_info =
        SSLManager::SerializeSecurityInfo(cert_id,
                                          cert_status,
                                          request->ssl_info().security_bits);
  } else {
    // We should not have any SSL state.
    DCHECK(!request->ssl_info().cert_status &&
           (request->ssl_info().security_bits == -1 ||
           request->ssl_info().security_bits == 0));
  }

  NotifyResponseStarted(request, info->render_process_host_id);
  return info->event_handler->OnResponseStarted(info->request_id,
                                                response.get());
}

void ResourceDispatcherHost::BeginRequestInternal(URLRequest* request,
                                                  bool mixed_content) {
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  GlobalRequestID global_id(info->render_process_host_id, info->request_id);
  pending_requests_[global_id] = request;
  if (mixed_content) {
    // We don't start the request in that case.  The SSLManager will potentially
    // change the request (potentially to indicate its content should be
    // filtered) and start it itself.
    SSLManager::OnMixedContentRequest(this, request, ui_loop_);
    return;
  }
  request->Start();

  // Make sure we have the load state monitor running
  update_load_states_timer_.Start();
}

// This test mirrors the decision that WebKit makes in
// WebFrameLoaderClient::dispatchDecidePolicyForMIMEType.
// static.
bool ResourceDispatcherHost::ShouldDownload(
    const std::string& mime_type, const std::string& content_disposition) {
  std::string type = StringToLowerASCII(mime_type);
  std::string disposition = StringToLowerASCII(content_disposition);

  // First, examine content-disposition.
  if (!disposition.empty()) {
    bool should_download = true;

    // Some broken sites just send ...
    //    Content-Disposition: ; filename="file"
    // ... screen those out here.
    if (disposition[0] == ';')
      should_download = false;

    if (disposition.compare(0, 6, "inline") == 0)
      should_download = false;

    // Some broken sites just send ...
    //    Content-Disposition: filename="file"
    // ... without a disposition token... Screen those out.
    if (disposition.compare(0, 8, "filename") == 0)
      should_download = false;

    // Also in use is Content-Disposition: name="file"
    if (disposition.compare(0, 4, "name") == 0)
      should_download = false;

    // We have a content-disposition of "attachment" or unknown.
    // RFC 2183, section 2.8 says that an unknown disposition
    // value should be treated as "attachment".
    if (should_download)
      return true;
  }

  // MIME type checking.
  if (net::IsSupportedMimeType(type))
    return false;

  // Finally, check the plugin service.
  bool allow_wildcard = false;
  return !plugin_service_->HavePluginFor(type, allow_wildcard);
}

bool ResourceDispatcherHost::PauseRequestIfNeeded(ExtraRequestInfo* info) {
  if (info->pause_count > 0)
    info->is_paused = true;

  return info->is_paused;
}

void ResourceDispatcherHost::ResumeRequest(const GlobalRequestID& request_id) {
  PendingRequestList::iterator i = pending_requests_.find(request_id);
  if (i == pending_requests_.end())  // The request may have been destroyed
    return;

  URLRequest* request = i->second;
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  if (!info->is_paused)
    return;

  RESOURCE_LOG("Resuming: " << i->second->url().spec());

  info->is_paused = false;

  if (info->has_started_reading)
    OnReadCompleted(i->second, info->paused_read_bytes);
  else
    OnResponseStarted(i->second);
}

bool ResourceDispatcherHost::Read(URLRequest *request, int *bytes_read) {
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  DCHECK(!info->is_paused);

  char* buf;
  int buf_size;
  if (!info->event_handler->OnWillRead(info->request_id, &buf, &buf_size, -1))
    return false;

  DCHECK(buf);
  DCHECK(buf_size > 0);

  info->has_started_reading = true;
  return request->Read(buf, buf_size, bytes_read);
}

void ResourceDispatcherHost::OnReadCompleted(URLRequest* request,
                                             int bytes_read) {
  DCHECK(request);
  RESOURCE_LOG("OnReadCompleted: " << request->url().spec());
  ExtraRequestInfo* info = ExtraInfoForRequest(request);
  if (PauseRequestIfNeeded(info)) {
    info->paused_read_bytes = bytes_read;
    RESOURCE_LOG("OnReadCompleted pausing: " << request->url().spec());
    return;
  }

  // Keep reading as long as we can.
  while (request->status().is_success() &&
         CompleteRead(request, &bytes_read)) {
    // The request can be paused if we realize that the renderer is not
    // servicing messages fast enough.
    if (info->pause_count > 0)
      break;

    if (!Read(request, &bytes_read))
      break;  // IO is pending.
  }

  if (PauseRequestIfNeeded(info)) {
    info->paused_read_bytes = bytes_read;
    RESOURCE_LOG("OnReadCompleted (CompleteRead) pausing: " <<
                 request->url().spec());
    return;
  }

  // If the status is not IO pending then we've either finished (success) or we
  // had an error.  Either way, we're done!
  if (!request->status().is_io_pending())
    OnResponseCompleted(request);
}

bool ResourceDispatcherHost::CompleteRead(URLRequest* request,
                                          int* bytes_read) {
  if (!request->status().is_success()) {
    NOTREACHED();
    return false;
  }

  ExtraRequestInfo* info = ExtraInfoForRequest(request);

  if (!info->event_handler->OnReadCompleted(info->request_id, bytes_read)) {
    CancelRequest(info->render_process_host_id, info->request_id, false);
    return false;
  }

  return *bytes_read != 0;
}

void ResourceDispatcherHost::OnResponseCompleted(URLRequest* request) {
  RESOURCE_LOG("OnResponseCompleted: " << request->url().spec());
  ExtraRequestInfo* info = ExtraInfoForRequest(request);

  if (info->event_handler->OnResponseCompleted(info->request_id,
                                               request->status())) {
    NotifyResponseCompleted(request, info->render_process_host_id);

    // The request is complete so we can remove it.
    RemovePendingRequest(info->render_process_host_id, info->request_id);
  }
  // If the handler's OnResponseCompleted returns false, we are deferring the
  // call until later.  We will notify the world and clean up when we resume.
}

void ResourceDispatcherHost::AddObserver(Observer* obs) {
  observer_list_.AddObserver(obs);
}

void ResourceDispatcherHost::RemoveObserver(Observer* obs) {
  observer_list_.RemoveObserver(obs);
}

URLRequest* ResourceDispatcherHost::GetURLRequest(
    GlobalRequestID request_id) const {
  // This should be running in the IO loop. io_loop_ can be NULL during the
  // unit_tests.
  DCHECK(MessageLoop::current() == io_loop_ && io_loop_);

  PendingRequestList::const_iterator i = pending_requests_.find(request_id);
  if (i == pending_requests_.end())
    return NULL;

  return i->second;
}

// A NotificationTask proxies a resource dispatcher notification from the IO
// thread to the UI thread.  It should be constructed on the IO thread and run
// in the UI thread.  Takes ownership of |details|.
class NotificationTask : public Task {
 public:
  NotificationTask(NotificationType type,
                   URLRequest* request,
                   ResourceRequestDetails* details)
  : type_(type),
    details_(details) {
    if (!tab_util::GetTabContentsID(request,
                                    &render_process_host_id_,
                                    &tab_contents_id_))
      NOTREACHED();
  }

  void Run() {
    // Find the tab associated with this request.
    TabContents* tab_contents =
        tab_util::GetTabContentsByID(render_process_host_id_, tab_contents_id_);

    if (tab_contents) {
      // Issue the notification.
      NotificationService::current()->
          Notify(type_,
                 Source<NavigationController>(tab_contents->controller()),
                 Details<ResourceRequestDetails>(details_.get()));
    }
  }

 private:
  // These IDs let us find the correct tab on the UI thread.
  int render_process_host_id_;
  int tab_contents_id_;

  // The type and details of the notification.
  NotificationType type_;
  scoped_ptr<ResourceRequestDetails> details_;
};

static int GetCertID(URLRequest* request, int render_process_host_id) {
  if (request->ssl_info().cert) {
    return CertStore::GetSharedInstance()->StoreCert(request->ssl_info().cert,
                                                     render_process_host_id);
  }
  // If there is no SSL info attached to this request, we must either be a non
  // secure request, or the request has been canceled or failed (before the SSL
  // info was populated), or the response is an error (we have seen 403, 404,
  // and 501) made up by the proxy.
  DCHECK(!request->url().SchemeIsSecure() ||
         (request->status().status() == URLRequestStatus::CANCELED) ||
         (request->status().status() == URLRequestStatus::FAILED) ||
         ((request->response_headers()->response_code() >= 400) &&
         (request->response_headers()->response_code() <= 599)));
  return 0;
}

void ResourceDispatcherHost::NotifyResponseStarted(URLRequest* request,
                                                   int render_process_host_id) {
  // Notify the observers on the IO thread.
  FOR_EACH_OBSERVER(Observer, observer_list_, OnRequestStarted(this, request));

  // Notify the observers on the UI thread.
  ui_loop_->PostTask(FROM_HERE,
      new NotificationTask(NOTIFY_RESOURCE_RESPONSE_STARTED, request,
                           new ResourceRequestDetails(request,
                               GetCertID(request, render_process_host_id))));
}

void ResourceDispatcherHost::NotifyResponseCompleted(
    URLRequest* request,
    int render_process_host_id) {
  // Notify the observers on the IO thread.
  FOR_EACH_OBSERVER(Observer, observer_list_,
                    OnResponseCompleted(this, request));

  // Notify the observers on the UI thread.
  ui_loop_->PostTask(FROM_HERE,
      new NotificationTask(NOTIFY_RESOURCE_RESPONSE_COMPLETED, request,
                           new ResourceRequestDetails(request,
                               GetCertID(request, render_process_host_id))));
}

void ResourceDispatcherHost::NofityReceivedRedirect(URLRequest* request,
                                                    int render_process_host_id,
                                                    const GURL& new_url) {
  // Notify the observers on the IO thread.
  FOR_EACH_OBSERVER(Observer, observer_list_,
                    OnReceivedRedirect(this, request, new_url));

  int cert_id = GetCertID(request, render_process_host_id);

  // Notify the observers on the UI thread.
  ui_loop_->PostTask(FROM_HERE,
      new NotificationTask(NOTIFY_RESOURCE_RECEIVED_REDIRECT, request,
                           new ResourceRedirectDetails(request,
                                                       cert_id,
                                                       new_url)));
}

namespace {

// This function attempts to return the "more interesting" load state of |a|
// and |b|.  We don't have temporal information about these load states
// (meaning we don't know when we transitioned into these states), so we just
// rank them according to how "interesting" the states are.
//
// We take advantage of the fact that the load states are an enumeration listed
// in the order in which they occur during the lifetime of a request, so we can
// regard states with larger numeric values as being further along toward
// completion.  We regard those states as more interesting to report since they
// represent progress.
//
// For example, by this measure "tranferring data" is a more interesting state
// than "resolving host" because when we are transferring data we are actually
// doing something that corresponds to changes that the user might observe,
// whereas waiting for a host name to resolve implies being stuck.
//
net::LoadState MoreInterestingLoadState(net::LoadState a, net::LoadState b) {
  return (a < b) ? b : a;
}

// Carries information about a load state change.
struct LoadInfo {
  GURL url;
  net::LoadState load_state;
};

// Map from ProcessID+ViewID pair to LoadState
typedef std::map<std::pair<int, int>, LoadInfo> LoadInfoMap;

// Used to marshall calls to LoadStateChanged from the IO to UI threads.  We do
// them all as a single task to avoid spamming the UI thread.
class LoadInfoUpdateTask : public Task {
 public:
  virtual void Run() {
    LoadInfoMap::const_iterator i;
    for (i = info_map.begin(); i != info_map.end(); ++i) {
      RenderViewHost* view =
          RenderViewHost::FromID(i->first.first, i->first.second);
      if (view)  // The view could be gone at this point.
        view->LoadStateChanged(i->second.url, i->second.load_state);
    }
  }
  LoadInfoMap info_map;
};

}  // namespace

void ResourceDispatcherHost::UpdateLoadStates() {
  // Populate this map with load state changes, and then send them on to the UI
  // thread where they can be passed along to the respective RVHs.
  LoadInfoMap info_map;

  PendingRequestList::const_iterator i;
  for (i = pending_requests_.begin(); i != pending_requests_.end(); ++i) {
    URLRequest* request = i->second;
    net::LoadState load_state = request->GetLoadState();
    ExtraRequestInfo* info = ExtraInfoForRequest(request);

    // We also poll for upload progress on this timer and send upload
    // progress ipc messages to the plugin process.
    MaybeUpdateUploadProgress(info, request);

    if (info->last_load_state != load_state) {
      info->last_load_state = load_state;

      std::pair<int, int> key(info->render_process_host_id,
                              info->render_view_id);
      net::LoadState to_insert;
      LoadInfoMap::iterator existing = info_map.find(key);
      if (existing == info_map.end()) {
        to_insert = load_state;
      } else {
        to_insert =
            MoreInterestingLoadState(existing->second.load_state, load_state);
        if (to_insert == existing->second.load_state)
          continue;
      }
      LoadInfo& load_info = info_map[key];
      load_info.url = request->url();
      load_info.load_state = to_insert;
    }
  }

  if (info_map.empty())
    return;

  LoadInfoUpdateTask* task = new LoadInfoUpdateTask;
  task->info_map.swap(info_map);
  ui_loop_->PostTask(FROM_HERE, task);
}

void ResourceDispatcherHost::MaybeUpdateUploadProgress(ExtraRequestInfo *info,
                                                       URLRequest *request) {
  if (!info->upload_size || info->waiting_for_upload_progress_ack ||
      !(request->load_flags() & net::LOAD_ENABLE_UPLOAD_PROGRESS))
    return;

  uint64 size = info->upload_size;
  uint64 position = request->GetUploadProgress();
  if (position == info->last_upload_position)
    return;  // no progress made since last time

  const uint64 kHalfPercentIncrements = 200;
  const TimeDelta kOneSecond = TimeDelta::FromMilliseconds(1000);

  uint64 amt_since_last = position - info->last_upload_position;
  TimeDelta time_since_last = TimeTicks::Now() - info->last_upload_ticks;

  bool is_finished = (size == position);
  bool enough_new_progress = (amt_since_last > (size / kHalfPercentIncrements));
  bool too_much_time_passed = time_since_last > kOneSecond;

  if (is_finished || enough_new_progress || too_much_time_passed) {
    info->event_handler->OnUploadProgress(info->request_id, position, size);
    info->waiting_for_upload_progress_ack = true;
    info->last_upload_ticks = TimeTicks::Now();
    info->last_upload_position = position;
  }
}