Browser process changes for Resource Timing sizes.

This is part of the changes to add the size fields to the
PerformanceResourceTiming API.

See the design doc at
https://docs.google.com/document/d/1ckL-rKLFRsdI4nn1golvQ6I1zRIvxgFkDXMrZb8KduY/edit

These are the browser-side changes to pass the "encodedBodySize" field to the
renderer. The field is named encoded_body_length in the //content code for
consistency with the existing encoded_data_length field.

For async resource fetches the value is passed one chunk at a time in the
ResourceMsg_InlinedDataChunkReceived and ResourceMsg_DataReceived IPCs. For sync
XHR it is passed in the ResourceResponseInfo struct.

BUG=467945

Review-Url: https://codereview.chromium.org/2092993002
Cr-Commit-Position: refs/heads/master@{#405015}
diff --git a/content/browser/loader/async_resource_handler.cc b/content/browser/loader/async_resource_handler.cc
index 518d274..91d0350b 100644
--- a/content/browser/loader/async_resource_handler.cc
+++ b/content/browser/loader/async_resource_handler.cc
@@ -29,6 +29,7 @@
 #include "content/public/browser/resource_dispatcher_host_delegate.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/resource_response.h"
+#include "ipc/ipc_message_macros.h"
 #include "net/base/io_buffer.h"
 #include "net/base/load_flags.h"
 #include "net/log/net_log.h"
@@ -72,6 +73,14 @@
   GetNumericArg("resource-buffer-max-allocation-size", &kMaxAllocationSize);
 }
 
+// Updates |*cached| to |updated| and returns the difference from the old
+// value.
+int TrackDifference(int64_t updated, int64_t* cached) {
+  int difference = updated - *cached;
+  *cached = updated;
+  return difference;
+}
+
 }  // namespace
 
 // Used when kOptimizeLoadingIPCForSmallResources is enabled.
@@ -117,6 +126,7 @@
   // Returns true if the received data is sent to the consumer.
   bool SendInlinedDataIfApplicable(int bytes_read,
                                    int encoded_data_length,
+                                   int encoded_body_length,
                                    IPC::Sender* sender,
                                    int request_id) {
     DCHECK(sender);
@@ -129,7 +139,7 @@
     leading_chunk_buffer_ = nullptr;
 
     sender->Send(new ResourceMsg_InlinedDataChunkReceived(
-        request_id, data, encoded_data_length));
+        request_id, data, encoded_data_length, encoded_body_length));
     return true;
   }
 
@@ -206,7 +216,8 @@
       inlining_helper_(new InliningHelper),
       last_upload_position_(0),
       waiting_for_upload_progress_ack_(false),
-      reported_transfer_size_(0) {
+      reported_transfer_size_(0),
+      reported_encoded_body_length_(0) {
   InitializeResourceBufferConstants();
 }
 
@@ -439,10 +450,12 @@
     return false;
 
   int encoded_data_length = CalculateEncodedDataLengthToReport();
+  int encoded_body_length = CalculateEncodedBodyLengthToReport();
 
   // Return early if InliningHelper handled the received data.
   if (inlining_helper_->SendInlinedDataIfApplicable(
-          bytes_read, encoded_data_length, filter, GetRequestID()))
+          bytes_read, encoded_data_length, encoded_body_length, filter,
+          GetRequestID()))
     return true;
 
   buffer_->ShrinkLastAllocation(bytes_read);
@@ -460,8 +473,9 @@
 
   int data_offset = buffer_->GetLastAllocationOffset();
 
-  filter->Send(new ResourceMsg_DataReceived(
-      GetRequestID(), data_offset, bytes_read, encoded_data_length));
+  filter->Send(new ResourceMsg_DataReceived(GetRequestID(), data_offset,
+                                            bytes_read, encoded_data_length,
+                                            encoded_body_length));
   ++pending_data_count_;
 
   if (!buffer_->CanAllocate()) {
@@ -565,10 +579,13 @@
 }
 
 int AsyncResourceHandler::CalculateEncodedDataLengthToReport() {
-  int64_t current_transfer_size = request()->GetTotalReceivedBytes();
-  int encoded_data_length = current_transfer_size - reported_transfer_size_;
-  reported_transfer_size_ = current_transfer_size;
-  return encoded_data_length;
+  return TrackDifference(request()->GetTotalReceivedBytes(),
+                         &reported_transfer_size_);
+}
+
+int AsyncResourceHandler::CalculateEncodedBodyLengthToReport() {
+  return TrackDifference(request()->GetRawBodyBytes(),
+                         &reported_encoded_body_length_);
 }
 
 void AsyncResourceHandler::RecordHistogram() {
diff --git a/content/browser/loader/async_resource_handler.h b/content/browser/loader/async_resource_handler.h
index 0991021..c087f16 100644
--- a/content/browser/loader/async_resource_handler.h
+++ b/content/browser/loader/async_resource_handler.h
@@ -14,6 +14,7 @@
 #include "base/timer/timer.h"
 #include "content/browser/loader/resource_handler.h"
 #include "content/browser/loader/resource_message_delegate.h"
+#include "content/common/content_export.h"
 #include "net/base/io_buffer.h"
 #include "url/gurl.h"
 
@@ -30,8 +31,8 @@
 
 // Used to complete an asynchronous resource request in response to resource
 // load events from the resource dispatcher host.
-class AsyncResourceHandler : public ResourceHandler,
-                             public ResourceMessageDelegate {
+class CONTENT_EXPORT AsyncResourceHandler : public ResourceHandler,
+                                            public ResourceMessageDelegate {
  public:
   AsyncResourceHandler(net::URLRequest* request,
                        ResourceDispatcherHostImpl* rdh);
@@ -70,6 +71,7 @@
   void OnDefer();
   bool CheckForSufficientResource();
   int CalculateEncodedDataLengthToReport();
+  int CalculateEncodedBodyLengthToReport();
   void RecordHistogram();
 
   scoped_refptr<ResourceBuffer> buffer_;
@@ -96,6 +98,7 @@
   base::RepeatingTimer progress_timer_;
 
   int64_t reported_transfer_size_;
+  int64_t reported_encoded_body_length_;
 
   DISALLOW_COPY_AND_ASSIGN(AsyncResourceHandler);
 };
diff --git a/content/browser/loader/async_resource_handler_unittest.cc b/content/browser/loader/async_resource_handler_unittest.cc
new file mode 100644
index 0000000..69f2f59
--- /dev/null
+++ b/content/browser/loader/async_resource_handler_unittest.cc
@@ -0,0 +1,339 @@
+// Copyright 2016 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/async_resource_handler.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/feature_list.h"
+#include "base/format_macros.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/process/process.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "content/browser/loader/resource_dispatcher_host_impl.h"
+#include "content/browser/loader/resource_loader.h"
+#include "content/browser/loader/resource_loader_delegate.h"
+#include "content/browser/loader/resource_message_filter.h"
+#include "content/browser/loader/resource_request_info_impl.h"
+#include "content/common/resource_messages.h"
+#include "content/common/resource_request.h"
+#include "content/public/browser/resource_context.h"
+#include "content/public/browser/resource_request_info.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/process_type.h"
+#include "content/public/common/resource_type.h"
+#include "content/public/test/mock_resource_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "ipc/ipc_message.h"
+#include "ipc/ipc_message_macros.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/ssl/client_cert_store.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "ui/base/page_transition_types.h"
+#include "url/gurl.h"
+
+namespace content {
+
+namespace {
+
+std::string GenerateHeader(size_t response_data_size) {
+  return base::StringPrintf(
+      "HTTP/1.1 200 OK\n"
+      "Content-type: text/html\n"
+      "Content-Length: %" PRIuS "\n",
+      response_data_size);
+}
+
+std::string GenerateData(size_t response_data_size) {
+  return std::string(response_data_size, 'a');
+}
+
+// This test job adds a Content-Length header and implements
+// GetTotalReceivedBytes().
+class TestJob : public net::URLRequestTestJob {
+ public:
+  TestJob(net::URLRequest* request,
+          net::NetworkDelegate* network_delegate,
+          size_t response_data_size)
+      : net::URLRequestTestJob(request,
+                               network_delegate,
+                               GenerateHeader(response_data_size),
+                               GenerateData(response_data_size),
+                               true) {}
+
+  static TestJob* CreateJob(net::URLRequest* request,
+                            net::NetworkDelegate* network_delegate,
+                            size_t response_data_size) {
+    return new TestJob(request, network_delegate, response_data_size);
+  }
+
+  // URLRequestJob implementation:
+  // TODO(ricea): Move this to URLRequestTestJob.
+  int64_t GetTotalReceivedBytes() const override {
+    std::string http_headers = net::HttpUtil::ConvertHeadersBackToHTTPResponse(
+        response_headers_->raw_headers());
+    return http_headers.size() + offset_;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(TestJob);
+};
+
+class TestJobProtocolHandler
+    : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+  TestJobProtocolHandler(size_t response_data_size)
+      : response_data_size_(response_data_size) {}
+
+  net::URLRequestJob* MaybeCreateJob(
+      net::URLRequest* request,
+      net::NetworkDelegate* network_delegate) const override {
+    return TestJob::CreateJob(request, network_delegate, response_data_size_);
+  }
+
+ private:
+  size_t response_data_size_;
+};
+
+// A subclass of ResourceMessageFilter that records IPC messages that are sent.
+class RecordingResourceMessageFilter : public ResourceMessageFilter {
+ public:
+  RecordingResourceMessageFilter(ResourceContext* resource_context,
+                                 net::URLRequestContext* request_context)
+      : ResourceMessageFilter(
+            0,
+            PROCESS_TYPE_RENDERER,
+            nullptr,
+            nullptr,
+            nullptr,
+            nullptr,
+            nullptr,
+            base::Bind(&RecordingResourceMessageFilter::GetContexts,
+                       base::Unretained(this))),
+        resource_context_(resource_context),
+        request_context_(request_context) {
+    set_peer_process_for_testing(base::Process::Current());
+  }
+
+  const std::vector<std::unique_ptr<IPC::Message>>& messages() const {
+    return messages_;
+  }
+
+  // IPC::Sender implementation:
+  bool Send(IPC::Message* message) override {
+    // Unpickle the base::SharedMemoryHandle to avoid warnings about
+    // "MessageAttachmentSet destroyed with unconsumed descriptors".
+    if (message->type() == ResourceMsg_SetDataBuffer::ID) {
+      ResourceMsg_SetDataBuffer::Param params;
+      ResourceMsg_SetDataBuffer::Read(message, &params);
+    }
+    messages_.push_back(base::WrapUnique(message));
+    return true;
+  }
+
+ private:
+  ~RecordingResourceMessageFilter() override {}
+
+  void GetContexts(ResourceType resource_type,
+                   int origin_pid,
+                   ResourceContext** resource_context,
+                   net::URLRequestContext** request_context) {
+    *resource_context = resource_context_;
+    *request_context = request_context_;
+  }
+
+  ResourceContext* const resource_context_;
+  net::URLRequestContext* const request_context_;
+  std::vector<std::unique_ptr<IPC::Message>> messages_;
+};
+
+class AsyncResourceHandlerTest : public ::testing::Test,
+                                 public ResourceLoaderDelegate {
+ protected:
+  AsyncResourceHandlerTest()
+      : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), context_(true) {}
+
+  void TearDown() override {
+    // Prevent memory leaks.
+    rdh_.Shutdown();
+    base::RunLoop().RunUntilIdle();
+  }
+
+  void CreateRequestWithResponseDataSize(size_t response_data_size) {
+    test_job_factory_.SetProtocolHandler(
+        "test", base::MakeUnique<TestJobProtocolHandler>(response_data_size));
+    context_.set_job_factory(&test_job_factory_);
+    context_.Init();
+    std::unique_ptr<net::URLRequest> request = context_.CreateRequest(
+        GURL("test:test"), net::DEFAULT_PRIORITY, nullptr);
+    resource_context_ = base::MakeUnique<MockResourceContext>(&context_);
+    filter_ =
+        new RecordingResourceMessageFilter(resource_context_.get(), &context_);
+    ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl(
+        PROCESS_TYPE_RENDERER,                 // process_type
+        0,                                     // child_id
+        0,                                     // route_id
+        -1,                                    // frame_tree_node_id
+        0,                                     // origin_pid
+        0,                                     // request_id
+        0,                                     // render_frame_id
+        false,                                 // is_main_frame
+        false,                                 // parent_is_main_frame
+        RESOURCE_TYPE_IMAGE,                   // resource_type
+        ui::PAGE_TRANSITION_LINK,              // transition_type
+        false,                                 // should_replace_current_entry
+        false,                                 // is_download
+        false,                                 // is_stream
+        false,                                 // allow_download
+        false,                                 // has_user_gesture
+        false,                                 // enable load timing
+        false,                                 // enable upload progress
+        false,                                 // do_not_prompt_for_login
+        blink::WebReferrerPolicyDefault,       // referrer_policy
+        blink::WebPageVisibilityStateVisible,  // visibility_state
+        resource_context_.get(),               // context
+        filter_->GetWeakPtr(),                 // filter
+        false,                                 // report_raw_headers
+        true,                                  // is_async
+        false,                                 // is_using_lofi
+        std::string(),                         // original_headers
+        nullptr,                               // body
+        false);                                // initiated_in_secure_context
+    info->AssociateWithRequest(request.get());
+    std::unique_ptr<AsyncResourceHandler> handler =
+        base::MakeUnique<AsyncResourceHandler>(request.get(), &rdh_);
+    loader_ = base::MakeUnique<ResourceLoader>(
+        std::move(request), std::move(handler), nullptr, this);
+  }
+
+  void StartRequestAndWaitWithResponseDataSize(size_t response_data_size) {
+    CreateRequestWithResponseDataSize(response_data_size);
+    loader_->StartRequest();
+    finish_waiter_.reset(new base::RunLoop);
+    finish_waiter_->Run();
+  }
+
+  scoped_refptr<RecordingResourceMessageFilter> filter_;
+
+ private:
+  // ResourceLoaderDelegate implementation:
+  ResourceDispatcherHostLoginDelegate* CreateLoginDelegate(
+      ResourceLoader* loader,
+      net::AuthChallengeInfo* auth_info) override {
+    return nullptr;
+  }
+
+  bool HandleExternalProtocol(ResourceLoader* loader,
+                              const GURL& url) override {
+    return false;
+  }
+  void DidStartRequest(ResourceLoader* loader) override {}
+  void DidReceiveRedirect(ResourceLoader* loader,
+                          const GURL& new_url) override {}
+  void DidReceiveResponse(ResourceLoader* loader) override {}
+  void DidFinishLoading(ResourceLoader* loader) override {
+    loader_.reset();
+    finish_waiter_->Quit();
+  }
+  std::unique_ptr<net::ClientCertStore> CreateClientCertStore(
+      ResourceLoader* loader) override {
+    return nullptr;
+  }
+
+  TestBrowserThreadBundle thread_bundle_;
+  ResourceDispatcherHostImpl rdh_;
+  net::TestURLRequestContext context_;
+  net::URLRequestJobFactoryImpl test_job_factory_;
+  std::unique_ptr<MockResourceContext> resource_context_;
+  std::unique_ptr<ResourceLoader> loader_;
+  std::unique_ptr<base::RunLoop> finish_waiter_;
+};
+
+TEST_F(AsyncResourceHandlerTest, Construct) {
+  CreateRequestWithResponseDataSize(1);
+}
+
+TEST_F(AsyncResourceHandlerTest, OneChunkLengths) {
+  // Larger than kInlinedLeadingChunkSize and smaller than
+  // kMaxAllocationSize.
+  StartRequestAndWaitWithResponseDataSize(4096);
+  const auto& messages = filter_->messages();
+  ASSERT_EQ(4u, messages.size());
+  ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
+  ResourceMsg_DataReceived::Param params;
+  ResourceMsg_DataReceived::Read(messages[2].get(), &params);
+
+  int encoded_data_length = std::get<3>(params);
+  EXPECT_EQ(4162, encoded_data_length);
+  int encoded_body_length = std::get<4>(params);
+  EXPECT_EQ(4096, encoded_body_length);
+}
+
+TEST_F(AsyncResourceHandlerTest, InlinedChunkLengths) {
+  // TODO(ricea): Remove this Feature-enabling code once the feature is on by
+  // default.
+  auto feature_list = base::MakeUnique<base::FeatureList>();
+  feature_list->InitializeFromCommandLine(
+      features::kOptimizeLoadingIPCForSmallResources.name, "");
+  base::FeatureList::ClearInstanceForTesting();
+  base::FeatureList::SetInstance(std::move(feature_list));
+
+  // Smaller than kInlinedLeadingChunkSize.
+  StartRequestAndWaitWithResponseDataSize(8);
+  const auto& messages = filter_->messages();
+  ASSERT_EQ(3u, messages.size());
+  ASSERT_EQ(ResourceMsg_InlinedDataChunkReceived::ID, messages[1]->type());
+  ResourceMsg_InlinedDataChunkReceived::Param params;
+  ResourceMsg_InlinedDataChunkReceived::Read(messages[1].get(), &params);
+
+  int encoded_data_length = std::get<2>(params);
+  EXPECT_EQ(71, encoded_data_length);
+  int encoded_body_length = std::get<3>(params);
+  EXPECT_EQ(8, encoded_body_length);
+}
+
+TEST_F(AsyncResourceHandlerTest, TwoChunksLengths) {
+  // Larger than kMaxAllocationSize.
+  StartRequestAndWaitWithResponseDataSize(64*1024);
+  const auto& messages = filter_->messages();
+  ASSERT_EQ(5u, messages.size());
+  ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[2]->type());
+  ResourceMsg_DataReceived::Param params;
+  ResourceMsg_DataReceived::Read(messages[2].get(), &params);
+
+  int encoded_data_length = std::get<3>(params);
+  EXPECT_EQ(32835, encoded_data_length);
+  int encoded_body_length = std::get<4>(params);
+  EXPECT_EQ(32768, encoded_body_length);
+
+  ASSERT_EQ(ResourceMsg_DataReceived::ID, messages[3]->type());
+  ResourceMsg_DataReceived::Read(messages[3].get(), &params);
+
+  encoded_data_length = std::get<3>(params);
+  EXPECT_EQ(32768, encoded_data_length);
+  encoded_body_length = std::get<4>(params);
+  EXPECT_EQ(32768, encoded_body_length);
+}
+
+}  // namespace
+
+}  // namespace content
diff --git a/content/browser/loader/sync_resource_handler.cc b/content/browser/loader/sync_resource_handler.cc
index 83277f0..c4217b29 100644
--- a/content/browser/loader/sync_resource_handler.cc
+++ b/content/browser/loader/sync_resource_handler.cc
@@ -127,6 +127,7 @@
 
   int total_transfer_size = request()->GetTotalReceivedBytes();
   result_.encoded_data_length = total_transfer_size_ + total_transfer_size;
+  result_.encoded_body_length = request()->GetRawBodyBytes();
 
   ResourceHostMsg_SyncLoad::WriteReplyParams(result_message_, result_);
   filter->Send(result_message_);
diff --git a/content/child/resource_dispatcher.cc b/content/child/resource_dispatcher.cc
index 461da5c..55fa5554 100644
--- a/content/child/resource_dispatcher.cc
+++ b/content/child/resource_dispatcher.cc
@@ -226,7 +226,8 @@
 void ResourceDispatcher::OnReceivedInlinedDataChunk(
     int request_id,
     const std::vector<char>& data,
-    int encoded_data_length) {
+    int encoded_data_length,
+    int encoded_body_length) {
   TRACE_EVENT0("loader", "ResourceDispatcher::OnReceivedInlinedDataChunk");
   DCHECK(!data.empty());
   DCHECK(base::FeatureList::IsEnabled(
@@ -254,7 +255,8 @@
 void ResourceDispatcher::OnReceivedData(int request_id,
                                         int data_offset,
                                         int data_length,
-                                        int encoded_data_length) {
+                                        int encoded_data_length,
+                                        int encoded_body_length) {
   TRACE_EVENT0("loader", "ResourceDispatcher::OnReceivedData");
   DCHECK_GT(data_length, 0);
   PendingRequestInfo* request_info = GetPendingRequestInfo(request_id);
diff --git a/content/child/resource_dispatcher.h b/content/child/resource_dispatcher.h
index 11062c6..8a56030 100644
--- a/content/child/resource_dispatcher.h
+++ b/content/child/resource_dispatcher.h
@@ -178,11 +178,13 @@
                        base::ProcessId renderer_pid);
   void OnReceivedInlinedDataChunk(int request_id,
                                   const std::vector<char>& data,
-                                  int encoded_data_length);
+                                  int encoded_data_length,
+                                  int encoded_body_length);
   void OnReceivedData(int request_id,
                       int data_offset,
                       int data_length,
-                      int encoded_data_length);
+                      int encoded_data_length,
+                      int encoded_body_length);
   void OnDownloadedData(int request_id, int data_len, int encoded_data_length);
   void OnRequestComplete(
       int request_id,
diff --git a/content/child/resource_dispatcher_unittest.cc b/content/child/resource_dispatcher_unittest.cc
index 07fbecf..5d072120 100644
--- a/content/child/resource_dispatcher_unittest.cc
+++ b/content/child/resource_dispatcher_unittest.cc
@@ -13,6 +13,7 @@
 #include <utility>
 #include <vector>
 
+#include "base/feature_list.h"
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/memory/shared_memory.h"
@@ -30,6 +31,7 @@
 #include "content/public/child/fixed_received_data.h"
 #include "content/public/child/request_peer.h"
 #include "content/public/child/resource_dispatcher_delegate.h"
+#include "content/public/common/content_features.h"
 #include "content/public/common/resource_response.h"
 #include "net/base/net_errors.h"
 #include "net/http/http_response_headers.h"
@@ -275,11 +277,19 @@
     memcpy(shared_memory_map_[request_id]->memory(), data.c_str(),
            data.length());
 
-    EXPECT_TRUE(dispatcher_->OnMessageReceived(
-        ResourceMsg_DataReceived(request_id, 0, data.length(), data.length())));
+    EXPECT_TRUE(dispatcher_->OnMessageReceived(ResourceMsg_DataReceived(
+        request_id, 0, data.length(), data.length(), data.length())));
   }
 
-  void NotifyDataDownloaded(int request_id, int decoded_length,
+  void NotifyInlinedDataChunkReceived(int request_id,
+                                      const std::vector<char>& data) {
+    auto size = data.size();
+    EXPECT_TRUE(dispatcher_->OnMessageReceived(
+        ResourceMsg_InlinedDataChunkReceived(request_id, data, size, size)));
+  }
+
+  void NotifyDataDownloaded(int request_id,
+                            int decoded_length,
                             int encoded_length) {
     EXPECT_TRUE(dispatcher_->OnMessageReceived(ResourceMsg_DataDownloaded(
         request_id, decoded_length, encoded_length)));
@@ -369,6 +379,35 @@
   EXPECT_EQ(0u, queued_messages());
 }
 
+// A simple request with an inline data response.
+TEST_F(ResourceDispatcherTest, ResponseWithInlinedData) {
+  auto feature_list = base::MakeUnique<base::FeatureList>();
+  feature_list->InitializeFromCommandLine(
+      features::kOptimizeLoadingIPCForSmallResources.name, std::string());
+  base::FeatureList::ClearInstanceForTesting();
+  base::FeatureList::SetInstance(std::move(feature_list));
+  std::unique_ptr<RequestInfo> request_info(CreateRequestInfo(false));
+  TestRequestPeer::Context peer_context;
+  StartAsync(*request_info.get(), NULL, &peer_context);
+
+  int id = ConsumeRequestResource();
+  EXPECT_EQ(0u, queued_messages());
+
+  NotifyReceivedResponse(id);
+  EXPECT_EQ(0u, queued_messages());
+  EXPECT_TRUE(peer_context.received_response);
+
+  std::vector<char> data(kTestPageContents,
+                         kTestPageContents + strlen(kTestPageContents));
+  NotifyInlinedDataChunkReceived(id, data);
+  EXPECT_EQ(0u, queued_messages());
+
+  NotifyRequestComplete(id, strlen(kTestPageContents));
+  EXPECT_EQ(kTestPageContents, peer_context.data);
+  EXPECT_TRUE(peer_context.complete);
+  EXPECT_EQ(0u, queued_messages());
+}
+
 // Tests that the request IDs are straight when there are two interleaving
 // requests.
 TEST_F(ResourceDispatcherTest, MultipleRequests) {
diff --git a/content/common/resource_messages.h b/content/common/resource_messages.h
index 0f343e72..a918d54 100644
--- a/content/common/resource_messages.h
+++ b/content/common/resource_messages.h
@@ -159,6 +159,7 @@
   IPC_STRUCT_TRAITS_MEMBER(has_major_certificate_errors)
   IPC_STRUCT_TRAITS_MEMBER(content_length)
   IPC_STRUCT_TRAITS_MEMBER(encoded_data_length)
+  IPC_STRUCT_TRAITS_MEMBER(encoded_body_length)
   IPC_STRUCT_TRAITS_MEMBER(appcache_id)
   IPC_STRUCT_TRAITS_MEMBER(appcache_manifest_url)
   IPC_STRUCT_TRAITS_MEMBER(load_timing)
@@ -307,19 +308,21 @@
 // Sent when a chunk of data from a resource request is ready, and the resource
 // is expected to be small enough to fit in the inlined buffer.
 // The data is sent as a part of IPC message.
-IPC_MESSAGE_CONTROL3(ResourceMsg_InlinedDataChunkReceived,
+IPC_MESSAGE_CONTROL4(ResourceMsg_InlinedDataChunkReceived,
                      int /* request_id */,
                      std::vector<char> /* data */,
-                     int /* encoded_data_length */)
+                     int /* encoded_data_length */,
+                     int /* encoded_body_length */)
 
 // Sent when some data from a resource request is ready.  The data offset and
 // length specify a byte range into the shared memory buffer provided by the
 // SetDataBuffer message.
-IPC_MESSAGE_CONTROL4(ResourceMsg_DataReceived,
+IPC_MESSAGE_CONTROL5(ResourceMsg_DataReceived,
                      int /* request_id */,
                      int /* data_offset */,
                      int /* data_length */,
-                     int /* encoded_data_length */)
+                     int /* encoded_data_length */,
+                     int /* encoded_body_length */)
 
 // Sent when some data from a resource request has been downloaded to
 // file. This is only called in the 'download_to_file' case and replaces
diff --git a/content/content_tests.gypi b/content/content_tests.gypi
index 1d634bc..bc70a69 100644
--- a/content/content_tests.gypi
+++ b/content/content_tests.gypi
@@ -507,6 +507,7 @@
       'browser/indexed_db/mock_indexed_db_database_callbacks.h',
       'browser/indexed_db/mock_indexed_db_factory.cc',
       'browser/indexed_db/mock_indexed_db_factory.h',
+      'browser/loader/async_resource_handler_unittest.cc',
       'browser/loader/async_revalidation_driver_unittest.cc',
       'browser/loader/async_revalidation_manager_unittest.cc',
       'browser/loader/mime_type_resource_handler_unittest.cc',
diff --git a/content/public/common/resource_response.cc b/content/public/common/resource_response.cc
index 3633eba8..0b94574 100644
--- a/content/public/common/resource_response.cc
+++ b/content/public/common/resource_response.cc
@@ -23,6 +23,7 @@
       head.has_major_certificate_errors;
   new_response->head.content_length = head.content_length;
   new_response->head.encoded_data_length = head.encoded_data_length;
+  new_response->head.encoded_body_length = head.encoded_body_length;
   new_response->head.appcache_id = head.appcache_id;
   new_response->head.appcache_manifest_url = head.appcache_manifest_url;
   new_response->head.load_timing = head.load_timing;
diff --git a/content/public/common/resource_response_info.cc b/content/public/common/resource_response_info.cc
index d471f2df..eed1a98ed 100644
--- a/content/public/common/resource_response_info.cc
+++ b/content/public/common/resource_response_info.cc
@@ -13,6 +13,7 @@
     : has_major_certificate_errors(false),
       content_length(-1),
       encoded_data_length(-1),
+      encoded_body_length(-1),
       appcache_id(kAppCacheNoCacheId),
       was_fetched_via_spdy(false),
       was_npn_negotiated(false),
diff --git a/content/public/common/resource_response_info.h b/content/public/common/resource_response_info.h
index 9c004aa..3c7a9c5 100644
--- a/content/public/common/resource_response_info.h
+++ b/content/public/common/resource_response_info.h
@@ -64,6 +64,10 @@
   // no data, contains -1.
   int64_t encoded_data_length;
 
+  // Length of the response body data before decompression. -1 unless the body
+  // has been read to the end.
+  int64_t encoded_body_length;
+
   // The appcache this response was loaded from, or kAppCacheNoCacheId.
   int64_t appcache_id;