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