Record Data Savings for Client-Side LoFi

This CL causes Chrome to record data savings for Client LoFi when it is
used, according to the Content-Range header if it's present (e.g.
"Content-Range: bytes 0-2047/10000" implies that the original resource
was 10000 bytes). This CL also adds a new PreviewsState bit field,
CLIENT_LOFI_AUTO_RELOAD, that is set when a Client LoFi image is auto
reloaded because of a decoding error, so that the browser process can
count the data used for the reload against the savings.

See design doc for more info:
https://docs.google.com/document/d/1CoZfTswPq67VaHKsr0OrkpPbWT87dhhvPq9hylOxrlU/edit

BUG=605347

Review-Url: https://codereview.chromium.org/2873793002
Cr-Commit-Position: refs/heads/master@{#470850}
diff --git a/components/data_reduction_proxy/content/browser/content_lofi_decider.cc b/components/data_reduction_proxy/content/browser/content_lofi_decider.cc
index d24fab4..dc203b9 100644
--- a/components/data_reduction_proxy/content/browser/content_lofi_decider.cc
+++ b/components/data_reduction_proxy/content/browser/content_lofi_decider.cc
@@ -221,4 +221,11 @@
          (request_info->GetPreviewsState() & content::CLIENT_LOFI_ON);
 }
 
+bool ContentLoFiDecider::IsClientLoFiAutoReloadRequest(
+    const net::URLRequest& request) const {
+  const content::ResourceRequestInfo* request_info =
+      content::ResourceRequestInfo::ForRequest(&request);
+  return request_info &&
+         (request_info->GetPreviewsState() & content::CLIENT_LOFI_AUTO_RELOAD);
+}
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/content/browser/content_lofi_decider.h b/components/data_reduction_proxy/content/browser/content_lofi_decider.h
index b71cd6b..b94a336 100644
--- a/components/data_reduction_proxy/content/browser/content_lofi_decider.h
+++ b/components/data_reduction_proxy/content/browser/content_lofi_decider.h
@@ -42,6 +42,8 @@
       net::HttpRequestHeaders* headers) const override;
   bool ShouldRecordLoFiUMA(const net::URLRequest& request) const override;
   bool IsClientLoFiImageRequest(const net::URLRequest& request) const override;
+  bool IsClientLoFiAutoReloadRequest(
+      const net::URLRequest& request) const override;
 
  private:
   DISALLOW_COPY_AND_ASSIGN(ContentLoFiDecider);
diff --git a/components/data_reduction_proxy/content/browser/content_lofi_decider_unittest.cc b/components/data_reduction_proxy/content/browser/content_lofi_decider_unittest.cc
index 46f5ee2..9405f8dc 100644
--- a/components/data_reduction_proxy/content/browser/content_lofi_decider_unittest.cc
+++ b/components/data_reduction_proxy/content/browser/content_lofi_decider_unittest.cc
@@ -818,4 +818,32 @@
   EXPECT_TRUE(lofi_decider->IsClientLoFiImageRequest(*request));
 }
 
+TEST_F(ContentLoFiDeciderTest, RequestIsClientLoFiAutoReload) {
+  // IsClientLoFiAutoReloadRequest() should return true for any request with the
+  // CLIENT_LOFI_AUTO_RELOAD bit set.
+
+  EXPECT_TRUE(ContentLoFiDecider().IsClientLoFiAutoReloadRequest(
+      *CreateRequestByType(content::RESOURCE_TYPE_IMAGE, false,
+                           content::CLIENT_LOFI_AUTO_RELOAD)));
+
+  EXPECT_TRUE(
+      ContentLoFiDecider().IsClientLoFiAutoReloadRequest(*CreateRequestByType(
+          content::RESOURCE_TYPE_IMAGE, true,
+          content::CLIENT_LOFI_AUTO_RELOAD | content::PREVIEWS_NO_TRANSFORM)));
+
+  EXPECT_TRUE(ContentLoFiDecider().IsClientLoFiAutoReloadRequest(
+      *CreateRequestByType(content::RESOURCE_TYPE_MAIN_FRAME, true,
+                           content::CLIENT_LOFI_AUTO_RELOAD)));
+
+  EXPECT_TRUE(ContentLoFiDecider().IsClientLoFiAutoReloadRequest(
+      *CreateRequestByType(content::RESOURCE_TYPE_SCRIPT, true,
+                           content::CLIENT_LOFI_AUTO_RELOAD)));
+
+  // IsClientLoFiAutoReloadRequest() should return false for any request without
+  // the CLIENT_LOFI_AUTO_RELOAD bit set.
+  EXPECT_FALSE(ContentLoFiDecider().IsClientLoFiAutoReloadRequest(
+      *CreateRequestByType(content::RESOURCE_TYPE_IMAGE, false,
+                           content::PREVIEWS_NO_TRANSFORM)));
+}
+
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
index b6e3940..6e464cb 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.cc
@@ -4,6 +4,7 @@
 
 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate.h"
 
+#include <algorithm>
 #include <limits>
 #include <utility>
 
@@ -117,18 +118,48 @@
                           received_content_length);
 }
 
-// Given a |request| that went through the Data Reduction Proxy, this function
-// estimates how many bytes would have been received if the response had been
-// received directly from the origin using HTTP/1.1 with a content length of
-// |adjusted_original_content_length|.
-int64_t EstimateOriginalReceivedBytes(const net::URLRequest& request) {
+// Estimate the size of the original headers of |request|. If |used_drp| is
+// true, then it's assumed that the original request would have used HTTP/1.1,
+// otherwise it assumes that the original request would have used the same
+// protocol as |request| did. This is to account for stuff like HTTP/2 header
+// compression.
+int64_t EstimateOriginalHeaderBytes(const net::URLRequest& request,
+                                    bool used_drp) {
+  if (used_drp) {
+    // TODO(sclittle): Remove headers added by Data Reduction Proxy when
+    // computing original size. https://crbug.com/535701.
+    return request.response_headers()->raw_headers().size();
+  }
+  return std::max<int64_t>(0, request.GetTotalReceivedBytes() -
+                                  request.received_response_content_length());
+}
+
+// Given a |request| that went through the Data Reduction Proxy if |used_drp| is
+// true, this function estimates how many bytes would have been received if the
+// response had been received directly from the origin without any data saver
+// optimizations.
+int64_t EstimateOriginalReceivedBytes(const net::URLRequest& request,
+                                      bool used_drp,
+                                      const LoFiDecider* lofi_decider) {
   if (request.was_cached() || !request.response_headers())
     return request.GetTotalReceivedBytes();
 
-  // TODO(sclittle): Remove headers added by Data Reduction Proxy when computing
-  // original size. http://crbug/535701.
-  return request.response_headers()->raw_headers().size() +
-         util::CalculateEffectiveOCL(request);
+  if (lofi_decider) {
+    if (lofi_decider->IsClientLoFiAutoReloadRequest(request))
+      return 0;
+
+    int64_t first, last, length;
+    if (lofi_decider->IsClientLoFiImageRequest(request) &&
+        request.response_headers()->GetContentRangeFor206(&first, &last,
+                                                          &length) &&
+        length > request.received_response_content_length()) {
+      return EstimateOriginalHeaderBytes(request, used_drp) + length;
+    }
+  }
+
+  return used_drp ? EstimateOriginalHeaderBytes(request, used_drp) +
+                        util::CalculateEffectiveOCL(request)
+                  : request.GetTotalReceivedBytes();
 }
 
 // Verifies that the chrome proxy related request headers are set correctly.
@@ -380,15 +411,15 @@
                                                               net_error);
 
   net::HttpRequestHeaders request_headers;
-  bool server_lofi = data_reduction_proxy_io_data_ &&
-                     request->response_headers() &&
+  bool server_lofi = request->response_headers() &&
                      IsEmptyImagePreview(*(request->response_headers()));
   bool client_lofi =
       data_reduction_proxy_io_data_ &&
       data_reduction_proxy_io_data_->lofi_decider() &&
       data_reduction_proxy_io_data_->lofi_decider()->IsClientLoFiImageRequest(
           *request);
-  if (server_lofi || client_lofi) {
+  if ((server_lofi || client_lofi) && data_reduction_proxy_io_data_ &&
+      data_reduction_proxy_io_data_->lofi_ui_service()) {
     data_reduction_proxy_io_data_->lofi_ui_service()->OnLoFiReponseReceived(
         *request);
   } else if (data_reduction_proxy_io_data_ && request->response_headers() &&
@@ -460,10 +491,11 @@
 
   // Estimate how many bytes would have been used if the DataReductionProxy was
   // not used, and record the data usage.
-  int64_t original_size = data_used;
-
-  if (request_type == VIA_DATA_REDUCTION_PROXY)
-    original_size = EstimateOriginalReceivedBytes(request);
+  int64_t original_size = EstimateOriginalReceivedBytes(
+      request, request_type == VIA_DATA_REDUCTION_PROXY,
+      data_reduction_proxy_io_data_
+          ? data_reduction_proxy_io_data_->lofi_decider()
+          : nullptr);
 
   std::string mime_type;
   if (request.response_headers())
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
index 1d651c9..902cc28 100644
--- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
+++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_network_delegate_unittest.cc
@@ -136,6 +136,7 @@
  public:
   TestLoFiDecider()
       : should_be_client_lofi_(false),
+        should_be_client_lofi_auto_reload_(false),
         should_request_lofi_resource_(false),
         ignore_is_using_data_reduction_proxy_check_(false) {}
   ~TestLoFiDecider() override {}
@@ -152,6 +153,10 @@
     should_be_client_lofi_ = should_be_client_lofi;
   }
 
+  void SetIsClientLoFiAutoReload(bool should_be_client_lofi_auto_reload) {
+    should_be_client_lofi_auto_reload_ = should_be_client_lofi_auto_reload;
+  }
+
   void MaybeSetAcceptTransformHeader(
       const net::URLRequest& request,
       bool is_previews_disabled,
@@ -200,12 +205,18 @@
     return should_be_client_lofi_;
   }
 
+  bool IsClientLoFiAutoReloadRequest(
+      const net::URLRequest& request) const override {
+    return should_be_client_lofi_auto_reload_;
+  }
+
   void ignore_is_using_data_reduction_proxy_check() {
     ignore_is_using_data_reduction_proxy_check_ = true;
   }
 
  private:
   bool should_be_client_lofi_;
+  bool should_be_client_lofi_auto_reload_;
   bool should_request_lofi_resource_;
   bool ignore_is_using_data_reduction_proxy_check_;
 };
@@ -255,6 +266,8 @@
   DataReductionProxyNetworkDelegateTest()
       : context_(true),
         context_storage_(&context_),
+        lofi_decider_(nullptr),
+        lofi_ui_service_(nullptr),
         ssl_socket_data_provider_(net::ASYNC, net::OK) {
     ssl_socket_data_provider_.next_proto = net::kProtoHTTP11;
     ssl_socket_data_provider_.cert = net::ImportCertFromFile(
@@ -1683,6 +1696,186 @@
   FetchURLRequestAndVerifyECTHeader(effective_connection_types[1], true, true);
 }
 
+class DataReductionProxyNetworkDelegateClientLoFiTest : public testing::Test {
+ public:
+  DataReductionProxyNetworkDelegateClientLoFiTest() : baseline_savings_(0) {}
+  ~DataReductionProxyNetworkDelegateClientLoFiTest() override;
+
+  void Reset() {
+    drp_test_context_.reset();
+    mock_socket_factory_.reset();
+    context_storage_.reset();
+
+    context_.reset(new net::TestURLRequestContext(true));
+    context_storage_.reset(new net::URLRequestContextStorage(context_.get()));
+    mock_socket_factory_.reset(new net::MockClientSocketFactory());
+    context_->set_client_socket_factory(mock_socket_factory_.get());
+
+    drp_test_context_ =
+        DataReductionProxyTestContext::Builder()
+            .WithURLRequestContext(context_.get())
+            .WithMockClientSocketFactory(mock_socket_factory_.get())
+            .Build();
+
+    drp_test_context_->AttachToURLRequestContext(context_storage_.get());
+    context_->Init();
+    base::RunLoop().RunUntilIdle();
+
+    baseline_savings_ =
+        drp_test_context()->settings()->GetTotalHttpContentLengthSaved();
+  }
+
+  void SetUpLoFiDecider(bool is_client_lofi_image,
+                        bool is_client_lofi_auto_reload) const {
+    std::unique_ptr<TestLoFiDecider> lofi_decider(new TestLoFiDecider());
+    lofi_decider->SetIsUsingClientLoFi(is_client_lofi_image);
+    lofi_decider->SetIsClientLoFiAutoReload(is_client_lofi_auto_reload);
+    drp_test_context_->io_data()->set_lofi_decider(
+        std::unique_ptr<LoFiDecider>(std::move(lofi_decider)));
+  }
+
+  int64_t GetSavings() const {
+    return drp_test_context()->settings()->GetTotalHttpContentLengthSaved() -
+           baseline_savings_;
+  }
+
+  net::TestURLRequestContext* context() const { return context_.get(); }
+  net::MockClientSocketFactory* mock_socket_factory() const {
+    return mock_socket_factory_.get();
+  }
+  DataReductionProxyTestContext* drp_test_context() const {
+    return drp_test_context_.get();
+  }
+
+ private:
+  base::MessageLoopForIO loop;
+  std::unique_ptr<net::TestURLRequestContext> context_;
+  std::unique_ptr<net::URLRequestContextStorage> context_storage_;
+  std::unique_ptr<net::MockClientSocketFactory> mock_socket_factory_;
+  std::unique_ptr<DataReductionProxyTestContext> drp_test_context_;
+  int64_t baseline_savings_;
+};
+
+DataReductionProxyNetworkDelegateClientLoFiTest::
+    ~DataReductionProxyNetworkDelegateClientLoFiTest() {}
+
+TEST_F(DataReductionProxyNetworkDelegateClientLoFiTest, DataSavingsNonDRP) {
+  const char kSimple200ResponseHeaders[] =
+      "HTTP/1.1 200 OK\r\n"
+      "Content-Length: 140\r\n\r\n";
+
+  const struct {
+    const char* headers;
+    size_t response_length;
+    bool is_client_lofi_image;
+    bool is_client_lofi_auto_reload;
+    int64_t expected_savings;
+  } tests[] = {
+      // 200 responses shouldn't see any savings.
+      {kSimple200ResponseHeaders, 140, false, false, 0},
+      {kSimple200ResponseHeaders, 140, true, false, 0},
+
+      // Client Lo-Fi Auto-reload responses should see negative savings.
+      {kSimple200ResponseHeaders, 140, false, true,
+       -(static_cast<int64_t>(sizeof(kSimple200ResponseHeaders) - 1) + 140)},
+      {kSimple200ResponseHeaders, 140, true, true,
+       -(static_cast<int64_t>(sizeof(kSimple200ResponseHeaders) - 1) + 140)},
+
+      // A range response that doesn't use Client Lo-Fi shouldn't see any
+      // savings.
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Range: bytes 0-2047/10000\r\n"
+       "Content-Length: 2048\r\n\r\n",
+       2048, false, false, 0},
+
+      // A Client Lo-Fi range response should see savings based on the
+      // Content-Range header.
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Range: bytes 0-2047/10000\r\n"
+       "Content-Length: 2048\r\n\r\n",
+       2048, true, false, 10000 - 2048},
+
+      // A Client Lo-Fi range response should see savings based on the
+      // Content-Range header, which in this case is 0 savings because the range
+      // response contained the entire resource.
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Range: bytes 0-999/1000\r\n"
+       "Content-Length: 1000\r\n\r\n",
+       1000, true, false, 0},
+
+      // Client Lo-Fi range responses that don't have a Content-Range with the
+      // full resource length shouldn't see any savings.
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Length: 2048\r\n\r\n",
+       2048, true, false, 0},
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Range: bytes 0-2047/*\r\n"
+       "Content-Length: 2048\r\n\r\n",
+       2048, true, false, 0},
+      {"HTTP/1.1 206 Partial Content\r\n"
+       "Content-Range: invalid_content_range\r\n"
+       "Content-Length: 2048\r\n\r\n",
+       2048, true, false, 0},
+  };
+
+  for (const auto& test : tests) {
+    Reset();
+    SetUpLoFiDecider(test.is_client_lofi_image,
+                     test.is_client_lofi_auto_reload);
+
+    std::string response_body(test.response_length, 'a');
+    net::MockRead reads[] = {net::MockRead(test.headers),
+                             net::MockRead(response_body.c_str()),
+                             net::MockRead(net::ASYNC, net::OK)};
+    net::StaticSocketDataProvider socket(reads, arraysize(reads), nullptr, 0);
+    mock_socket_factory()->AddSocketDataProvider(&socket);
+
+    net::TestDelegate test_delegate;
+    std::unique_ptr<net::URLRequest> request = context()->CreateRequest(
+        GURL("http://example.com"), net::RequestPriority::IDLE, &test_delegate);
+
+    request->Start();
+    base::RunLoop().RunUntilIdle();
+
+    EXPECT_EQ(test.expected_savings, GetSavings()) << (&test - tests);
+  }
+}
+
+TEST_F(DataReductionProxyNetworkDelegateClientLoFiTest, DataSavingsThroughDRP) {
+  Reset();
+  drp_test_context()->EnableDataReductionProxyWithSecureProxyCheckSuccess();
+  SetUpLoFiDecider(true, false);
+
+  const char kHeaders[] =
+      "HTTP/1.1 206 Partial Content\r\n"
+      "Content-Range: bytes 0-2047/10000\r\n"
+      "Content-Length: 2048\r\n"
+      "Via: 1.1 Chrome-Compression-Proxy\r\n"
+      "X-Original-Content-Length: 3000\r\n\r\n";
+
+  std::string response_body(2048, 'a');
+  net::MockRead reads[] = {net::MockRead(kHeaders),
+                           net::MockRead(response_body.c_str()),
+                           net::MockRead(net::ASYNC, net::OK)};
+  net::StaticSocketDataProvider socket(reads, arraysize(reads), nullptr, 0);
+  mock_socket_factory()->AddSocketDataProvider(&socket);
+
+  net::TestDelegate test_delegate;
+  std::unique_ptr<net::URLRequest> request = context()->CreateRequest(
+      GURL("http://example.com"), net::RequestPriority::IDLE, &test_delegate);
+
+  request->Start();
+  base::RunLoop().RunUntilIdle();
+
+  // Since the Data Reduction Proxy is enabled, the length of the raw headers
+  // should be used in the estimated original size. The X-OCL should be ignored.
+  EXPECT_EQ(static_cast<int64_t>(net::HttpUtil::AssembleRawHeaders(
+                                     kHeaders, sizeof(kHeaders) - 1)
+                                     .size() +
+                                 10000 - request->GetTotalReceivedBytes()),
+            GetSavings());
+}
+
 }  // namespace
 
 }  // namespace data_reduction_proxy
diff --git a/components/data_reduction_proxy/core/common/lofi_decider.h b/components/data_reduction_proxy/core/common/lofi_decider.h
index c0b3858..867ecf50 100644
--- a/components/data_reduction_proxy/core/common/lofi_decider.h
+++ b/components/data_reduction_proxy/core/common/lofi_decider.h
@@ -67,6 +67,11 @@
   // Returns whether the request was a client-side Lo-Fi image request.
   virtual bool IsClientLoFiImageRequest(
       const net::URLRequest& request) const = 0;
+
+  // Returns true if the request is for a client-side Lo-Fi image that is being
+  // automatically reloaded because of a decoding error.
+  virtual bool IsClientLoFiAutoReloadRequest(
+      const net::URLRequest& request) const = 0;
 };
 
 }  // namespace data_reduction_proxy
diff --git a/content/public/common/previews_state.h b/content/public/common/previews_state.h
index 07ac08b..74e4f39b 100644
--- a/content/public/common/previews_state.h
+++ b/content/public/common/previews_state.h
@@ -27,19 +27,22 @@
 // content of web pages to improve data savings and / or performance. This enum
 // determines which Previews types to request.
 enum PreviewsTypes {
-  PREVIEWS_UNSPECIFIED = 0,        // Let the browser process decide whether or
-                                   // not to request Preview types.
-  SERVER_LOFI_ON = 1 << 0,         // Request a Lo-Fi version of the resource
-                                   // from the server.
-  CLIENT_LOFI_ON = 1 << 1,         // Request a Lo-Fi version of the resource
-                                   // from the client.
-  SERVER_LITE_PAGE_ON = 1 << 2,    // Request a Lite Page version of the
-                                   // resource from the server.
-  PREVIEWS_NO_TRANSFORM = 1 << 3,  // Explicitly forbid Previews
-                                   // transformations.
-  PREVIEWS_OFF = 1 << 4,           // Request a normal (non-Preview) version of
-                                   // the resource. Server transformations may
-                                   // still happen if the page is heavy.
+  PREVIEWS_UNSPECIFIED = 0,  // Let the browser process decide whether or
+                             // not to request Preview types.
+  SERVER_LOFI_ON = 1 << 0,   // Request a Lo-Fi version of the resource
+                             // from the server.
+  CLIENT_LOFI_ON = 1 << 1,   // Request a Lo-Fi version of the resource
+                             // from the client.
+  CLIENT_LOFI_AUTO_RELOAD = 1 << 2,  // Request the original version of the
+                                     // resource after a decoding error occurred
+                                     // when attempting to use Client Lo-Fi.
+  SERVER_LITE_PAGE_ON = 1 << 3,      // Request a Lite Page version of the
+                                     // resource from the server.
+  PREVIEWS_NO_TRANSFORM = 1 << 4,    // Explicitly forbid Previews
+                                     // transformations.
+  PREVIEWS_OFF = 1 << 5,  // Request a normal (non-Preview) version of
+                          // the resource. Server transformations may
+                          // still happen if the page is heavy.
   PREVIEWS_STATE_LAST = PREVIEWS_OFF
 };
 
@@ -51,6 +54,8 @@
                             blink::WebURLRequest::kServerLoFiOn);
 STATIC_ASSERT_PREVIEWS_ENUM(CLIENT_LOFI_ON,
                             blink::WebURLRequest::kClientLoFiOn);
+STATIC_ASSERT_PREVIEWS_ENUM(CLIENT_LOFI_AUTO_RELOAD,
+                            blink::WebURLRequest::kClientLoFiAutoReload);
 STATIC_ASSERT_PREVIEWS_ENUM(SERVER_LITE_PAGE_ON,
                             blink::WebURLRequest::kServerLitePageOn);
 STATIC_ASSERT_PREVIEWS_ENUM(PREVIEWS_NO_TRANSFORM,
diff --git a/third_party/WebKit/Source/core/loader/resource/ImageResource.cpp b/third_party/WebKit/Source/core/loader/resource/ImageResource.cpp
index 0537ade..c5c664d4 100644
--- a/third_party/WebKit/Source/core/loader/resource/ImageResource.cpp
+++ b/third_party/WebKit/Source/core/loader/resource/ImageResource.cpp
@@ -494,7 +494,20 @@
 
   SetCachePolicyBypassingCache();
 
-  SetPreviewsStateNoTransform();
+  // The reloaded image should not use any previews transformations.
+  WebURLRequest::PreviewsState previews_state_for_reload =
+      WebURLRequest::kPreviewsNoTransform;
+
+  if (policy == kReloadIfNeeded && (GetResourceRequest().GetPreviewsState() &
+                                    WebURLRequest::kClientLoFiOn)) {
+    // If the image attempted to use Client LoFi, but encountered a decoding
+    // error and is being automatically reloaded, then also set the appropriate
+    // PreviewsState bit for that. This allows the embedder to count the
+    // bandwidth used for this reload against the data savings of the initial
+    // response.
+    previews_state_for_reload |= WebURLRequest::kClientLoFiAutoReload;
+  }
+  SetPreviewsState(previews_state_for_reload);
 
   if (placeholder_option_ != PlaceholderOption::kDoNotReloadPlaceholder)
     ClearRangeRequestHeader();
diff --git a/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp b/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
index 67a803a..065b2d2 100644
--- a/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
+++ b/third_party/WebKit/Source/core/loader/resource/ImageResourceTest.cpp
@@ -1245,50 +1245,69 @@
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderPartialContentWithoutDimensions) {
-  KURL test_url(kParsedURLString, kTestURL);
-  ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
+  const struct {
+    WebURLRequest::PreviewsState initial_previews_state;
+    WebURLRequest::PreviewsState expected_reload_previews_state;
+  } tests[] = {
+      {WebURLRequest::kPreviewsUnspecified,
+       WebURLRequest::kPreviewsNoTransform},
+      {WebURLRequest::kClientLoFiOn, WebURLRequest::kPreviewsNoTransform |
+                                         WebURLRequest::kClientLoFiAutoReload},
+  };
 
-  FetchParameters params{ResourceRequest(test_url), FetchInitiatorInfo()};
-  params.SetAllowImagePlaceholder();
-  ImageResource* image_resource = ImageResource::Fetch(params, CreateFetcher());
-  EXPECT_EQ(FetchParameters::kAllowPlaceholder,
-            params.GetPlaceholderImageRequestType());
-  EXPECT_EQ("bytes=0-2047",
-            image_resource->GetResourceRequest().HttpHeaderField("range"));
-  EXPECT_TRUE(image_resource->ShouldShowPlaceholder());
-  std::unique_ptr<MockImageResourceObserver> observer =
-      MockImageResourceObserver::Create(image_resource->GetContent());
+  for (const auto& test : tests) {
+    KURL test_url(kParsedURLString, kTestURL);
+    ScopedMockedURLLoad scoped_mocked_url_load(test_url, GetTestFilePath());
 
-  // TODO(hiroshige): Make the range request header and partial content length
-  // consistent. https://crbug.com/689760.
-  ResourceResponse partial_response(test_url, "image/jpeg",
-                                    kJpegImageSubrangeWithoutDimensionsLength,
-                                    g_null_atom);
-  partial_response.SetHTTPStatusCode(206);
-  partial_response.SetHTTPHeaderField(
-      "content-range",
-      BuildContentRange(kJpegImageSubrangeWithoutDimensionsLength,
-                        sizeof(kJpegImage)));
+    ResourceRequest resource_request(test_url);
+    resource_request.SetPreviewsState(test.initial_previews_state);
+    FetchParameters params{resource_request, FetchInitiatorInfo()};
 
-  image_resource->Loader()->DidReceiveResponse(
-      WrappedResourceResponse(partial_response));
-  image_resource->Loader()->DidReceiveData(
-      reinterpret_cast<const char*>(kJpegImage),
-      kJpegImageSubrangeWithoutDimensionsLength);
+    params.SetAllowImagePlaceholder();
+    ImageResource* image_resource =
+        ImageResource::Fetch(params, CreateFetcher());
+    EXPECT_EQ(FetchParameters::kAllowPlaceholder,
+              params.GetPlaceholderImageRequestType());
+    EXPECT_EQ("bytes=0-2047",
+              image_resource->GetResourceRequest().HttpHeaderField("range"));
+    EXPECT_TRUE(image_resource->ShouldShowPlaceholder());
+    std::unique_ptr<MockImageResourceObserver> observer =
+        MockImageResourceObserver::Create(image_resource->GetContent());
 
-  EXPECT_EQ(0, observer->ImageChangedCount());
+    // TODO(hiroshige): Make the range request header and partial content length
+    // consistent. https://crbug.com/689760.
+    ResourceResponse partial_response(test_url, "image/jpeg",
+                                      kJpegImageSubrangeWithoutDimensionsLength,
+                                      g_null_atom);
+    partial_response.SetHTTPStatusCode(206);
+    partial_response.SetHTTPHeaderField(
+        "content-range",
+        BuildContentRange(kJpegImageSubrangeWithoutDimensionsLength,
+                          sizeof(kJpegImage)));
 
-  image_resource->Loader()->DidFinishLoading(
-      0.0, kJpegImageSubrangeWithoutDimensionsLength,
-      kJpegImageSubrangeWithoutDimensionsLength,
-      kJpegImageSubrangeWithoutDimensionsLength);
+    image_resource->Loader()->DidReceiveResponse(
+        WrappedResourceResponse(partial_response));
+    image_resource->Loader()->DidReceiveData(
+        reinterpret_cast<const char*>(kJpegImage),
+        kJpegImageSubrangeWithoutDimensionsLength);
 
-  EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
-  EXPECT_EQ(2, observer->ImageChangedCount());
+    EXPECT_EQ(0, observer->ImageChangedCount());
 
-  TestThatReloadIsStartedThenServeReload(
-      test_url, image_resource, image_resource->GetContent(), observer.get(),
-      WebCachePolicy::kBypassingCache);
+    image_resource->Loader()->DidFinishLoading(
+        0.0, kJpegImageSubrangeWithoutDimensionsLength,
+        kJpegImageSubrangeWithoutDimensionsLength,
+        kJpegImageSubrangeWithoutDimensionsLength);
+
+    EXPECT_FALSE(observer->ImageNotifyFinishedCalled());
+    EXPECT_EQ(2, observer->ImageChangedCount());
+
+    TestThatReloadIsStartedThenServeReload(
+        test_url, image_resource, image_resource->GetContent(), observer.get(),
+        WebCachePolicy::kBypassingCache);
+
+    EXPECT_EQ(test.expected_reload_previews_state,
+              image_resource->GetResourceRequest().GetPreviewsState());
+  }
 }
 
 TEST(ImageResourceTest, FetchAllowPlaceholderThenDisallowPlaceholder) {
diff --git a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
index 8d71536..ec544791 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
+++ b/third_party/WebKit/Source/platform/loader/fetch/Resource.cpp
@@ -922,8 +922,8 @@
   resource_request_.SetCachePolicy(WebCachePolicy::kBypassingCache);
 }
 
-void Resource::SetPreviewsStateNoTransform() {
-  resource_request_.SetPreviewsState(WebURLRequest::kPreviewsNoTransform);
+void Resource::SetPreviewsState(WebURLRequest::PreviewsState previews_state) {
+  resource_request_.SetPreviewsState(previews_state);
 }
 
 void Resource::ClearRangeRequestHeader() {
diff --git a/third_party/WebKit/Source/platform/loader/fetch/Resource.h b/third_party/WebKit/Source/platform/loader/fetch/Resource.h
index c4ae381..2f4bcad 100644
--- a/third_party/WebKit/Source/platform/loader/fetch/Resource.h
+++ b/third_party/WebKit/Source/platform/loader/fetch/Resource.h
@@ -406,7 +406,7 @@
   }
 
   void SetCachePolicyBypassingCache();
-  void SetPreviewsStateNoTransform();
+  void SetPreviewsState(WebURLRequest::PreviewsState);
   void ClearRangeRequestHeader();
 
   SharedBuffer* Data() const { return data_.Get(); }
diff --git a/third_party/WebKit/public/platform/WebURLRequest.h b/third_party/WebKit/public/platform/WebURLRequest.h
index e559ac77..ba9dcb8 100644
--- a/third_party/WebKit/public/platform/WebURLRequest.h
+++ b/third_party/WebKit/public/platform/WebURLRequest.h
@@ -150,19 +150,22 @@
   // The Previews types which determines whether to request a Preview version of
   // the resource.
   enum PreviewsTypes {
-    kPreviewsUnspecified = 0,       // Let the browser process decide whether or
-                                    // not to request Preview types.
-    kServerLoFiOn = 1 << 0,         // Request a Lo-Fi version of the resource
-                                    // from the server.
-    kClientLoFiOn = 1 << 1,         // Request a Lo-Fi version of the resource
-                                    // from the client.
-    kServerLitePageOn = 1 << 2,     // Request a Lite Page version of the
-                                    // resource from the server.
-    kPreviewsNoTransform = 1 << 3,  // Explicitly forbid Previews
-                                    // transformations.
-    kPreviewsOff = 1 << 4,          // Request a normal (non-Preview) version of
-                                    // the resource. Server transformations may
-                                    // still happen if the page is heavy.
+    kPreviewsUnspecified = 0,  // Let the browser process decide whether or
+                               // not to request Preview types.
+    kServerLoFiOn = 1 << 0,    // Request a Lo-Fi version of the resource
+                               // from the server.
+    kClientLoFiOn = 1 << 1,    // Request a Lo-Fi version of the resource
+                               // from the client.
+    kClientLoFiAutoReload = 1 << 2,  // Request the original version of the
+                                     // resource after a decoding error occurred
+                                     // when attempting to use Client Lo-Fi.
+    kServerLitePageOn = 1 << 3,      // Request a Lite Page version of the
+                                     // resource from the server.
+    kPreviewsNoTransform = 1 << 4,   // Explicitly forbid Previews
+                                     // transformations.
+    kPreviewsOff = 1 << 5,  // Request a normal (non-Preview) version of
+                            // the resource. Server transformations may
+                            // still happen if the page is heavy.
     kPreviewsStateLast = kPreviewsOff
   };