Add an exponential backoff to rechecking the app list doodle.
[chromium-blink-merge.git] / components / webp_transcode / webp_network_client.mm
blobb39faa39626c85359733c1d081c257635154af1e
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "components/webp_transcode/webp_network_client.h"
7 #include "base/bind.h"
8 #include "base/compiler_specific.h"
9 #include "base/location.h"
10 #include "base/logging.h"
11 #include "base/mac/bind_objc_block.h"
12 #include "base/mac/scoped_nsobject.h"
13 #include "base/sequenced_task_runner.h"
14 #include "base/single_thread_task_runner.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/sys_string_conversions.h"
17 #include "base/thread_task_runner_handle.h"
18 #include "components/webp_transcode/webp_decoder.h"
19 #include "net/base/net_errors.h"
20 #include "net/http/http_request_headers.h"
21 #include "net/url_request/url_request.h"
23 namespace net {
24 class URLRequest;
27 using namespace webp_transcode;
29 namespace {
31 // MIME type for WebP images.
32 const char kWebPMimeType[] = "image/webp";
33 NSString* const kNSWebPMimeType = @"image/webp";
35 NSURLResponse* NewImageResponse(NSURLResponse* webp_response,
36                                 size_t content_length,
37                                 WebpDecoder::DecodedImageFormat format) {
38   DCHECK(webp_response);
40   NSString* mime_type = nil;
41   switch (format) {
42     case WebpDecoder::JPEG:
43       mime_type = @"image/jpeg";
44       break;
45     case WebpDecoder::PNG:
46       mime_type = @"image/png";
47       break;
48     case WebpDecoder::TIFF:
49       mime_type = @"image/tiff";
50       break;
51     case WebpDecoder::DECODED_FORMAT_COUNT:
52       NOTREACHED();
53       break;
54   }
55   DCHECK(mime_type);
57   if ([webp_response isKindOfClass:[NSHTTPURLResponse class]]) {
58     NSHTTPURLResponse* http_response =
59         static_cast<NSHTTPURLResponse*>(webp_response);
60     NSMutableDictionary* header_fields = [NSMutableDictionary
61         dictionaryWithDictionary:[http_response allHeaderFields]];
62     [header_fields setObject:[NSString stringWithFormat:@"%zu", content_length]
63                       forKey:@"Content-Length"];
64     [header_fields setObject:mime_type forKey:@"Content-Type"];
65     return [[NSHTTPURLResponse alloc] initWithURL:[http_response URL]
66                                        statusCode:[http_response statusCode]
67                                       HTTPVersion:@"HTTP/1.1"
68                                      headerFields:header_fields];
69   } else {
70     return [[NSURLResponse alloc] initWithURL:[webp_response URL]
71                                      MIMEType:mime_type
72                         expectedContentLength:content_length
73                              textEncodingName:[webp_response textEncodingName]];
74   }
77 class WebpDecoderDelegate : public WebpDecoder::Delegate {
78  public:
79   WebpDecoderDelegate(id<CRNNetworkClientProtocol> client,
80                       const base::Time& request_creation_time,
81                       const scoped_refptr<base::TaskRunner>& callback_runner)
82       : underlying_client_([client retain]),
83         callback_task_runner_(callback_runner),
84         request_creation_time_(request_creation_time) {
85     DCHECK(underlying_client_.get());
86   }
88   void SetOriginalResponse(
89       const base::scoped_nsobject<NSURLResponse>& response) {
90     original_response_.reset([response retain]);
91   }
93   // WebpDecoder::Delegate methods.
94   void OnFinishedDecoding(bool success) override {
95     base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
96         [underlying_client_ retain]);
97     if (success) {
98       callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
99         [block_client didFinishLoading];
100       }));
101     } else {
102       DLOG(WARNING) << "WebP decoding failed "
103                     << base::SysNSStringToUTF8(
104                            [[original_response_ URL] absoluteString]);
105       void (^errorBlock)(void) = ^{
106         [block_client didFailWithNSErrorCode:NSURLErrorCannotDecodeContentData
107                                 netErrorCode:net::ERR_CONTENT_DECODING_FAILED];
108       };
109       callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(errorBlock));
110     }
111   }
113   void SetImageFeatures(size_t total_size,
114                         WebpDecoder::DecodedImageFormat format) override {
115     base::scoped_nsobject<NSURLResponse> imageResponse(
116         NewImageResponse(original_response_, total_size, format));
117     DCHECK(imageResponse);
118     base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
119         [underlying_client_ retain]);
120     callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
121       [block_client didReceiveResponse:imageResponse];
122     }));
123   }
125   void OnDataDecoded(NSData* data) override {
126     base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client(
127         [underlying_client_ retain]);
128     callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{
129       [block_client didLoadData:data];
130     }));
131   }
133  private:
134   ~WebpDecoderDelegate() override {}
136   base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> underlying_client_;
137   base::scoped_nsobject<NSURLResponse> original_response_;
138   scoped_refptr<base::TaskRunner> callback_task_runner_;
139   base::Time request_creation_time_;
142 }  // namespace
144 @interface WebPNetworkClient () {
145   scoped_refptr<webp_transcode::WebpDecoder> _webpDecoder;
146   scoped_refptr<WebpDecoderDelegate> _webpDecoderDelegate;
147   scoped_refptr<base::SequencedTaskRunner> _taskRunner;
148   base::Time _requestCreationTime;
150 @end
152 @implementation WebPNetworkClient
154 - (instancetype)init {
155   NOTREACHED() << "Use |-initWithTaskRunner:| instead";
156   return nil;
159 - (instancetype)initWithTaskRunner:
160     (const scoped_refptr<base::SequencedTaskRunner>&)runner {
161   if (self = [super init]) {
162     DCHECK(runner);
163     _taskRunner = runner;
164   }
165   return self;
168 - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest {
169   // Append 'image/webp' to the outgoing 'Accept' header.
170   const net::HttpRequestHeaders& headers =
171       nativeRequest->extra_request_headers();
172   std::string acceptHeader;
173   if (headers.GetHeader("Accept", &acceptHeader)) {
174     // Add 'image/webp' if it isn't in the Accept header yet.
175     if (acceptHeader.find(kWebPMimeType) == std::string::npos) {
176       acceptHeader += std::string(",") + kWebPMimeType;
177       nativeRequest->SetExtraRequestHeaderByName("Accept", acceptHeader, true);
178     }
179   } else {
180     // All requests should already have an Accept: header, so this case
181     // should never happen outside of unit tests.
182     nativeRequest->SetExtraRequestHeaderByName("Accept", kWebPMimeType, false);
183   }
184   [super didCreateNativeRequest:nativeRequest];
187 - (void)didLoadData:(NSData*)data {
188   if (_webpDecoder.get()) {
189     // |data| is assumed to be immutable.
190     base::scoped_nsobject<NSData> scopedData([data retain]);
191     _taskRunner->PostTask(FROM_HERE, base::Bind(&WebpDecoder::OnDataReceived,
192                                                 _webpDecoder, scopedData));
193   } else {
194     [super didLoadData:data];
195   }
198 - (void)didReceiveResponse:(NSURLResponse*)response {
199   DCHECK(self.underlyingClient);
200   NSString* responseMimeType = [response MIMEType];
201   if (responseMimeType &&
202       [responseMimeType caseInsensitiveCompare:kNSWebPMimeType] ==
203           NSOrderedSame) {
204     _webpDecoderDelegate =
205         new WebpDecoderDelegate(self.underlyingClient, _requestCreationTime,
206                                 base::ThreadTaskRunnerHandle::Get());
207     _webpDecoder = new webp_transcode::WebpDecoder(_webpDecoderDelegate.get());
208     base::scoped_nsobject<NSURLResponse> scoped_response([response copy]);
209     _taskRunner->PostTask(FROM_HERE,
210                           base::Bind(&WebpDecoderDelegate::SetOriginalResponse,
211                                      _webpDecoderDelegate, scoped_response));
212     // Do not call super here, the WebpDecoderDelegate will update the mime type
213     // and call |-didReceiveResponse:|.
214   } else {
215     // If this isn't a WebP, pass the call up the chain.
216     // TODO(marq): It would be nice if at this point the client could remove
217     // itself from the client stack.
218     [super didReceiveResponse:response];
219   }
222 - (void)didFinishLoading {
223   if (_webpDecoder.get()) {
224     _taskRunner->PostTask(FROM_HERE,
225                           base::Bind(&WebpDecoder::Stop, _webpDecoder));
226   } else {
227     [super didFinishLoading];
228   }
231 @end