Chromium Code Reviews| 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 "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" | |
| 22 | |
| 23 namespace net { | |
| 24 class URLRequest; | |
| 25 } | |
| 26 | |
| 27 using namespace webp_transcode; | |
| 28 | |
| 29 namespace { | |
| 30 | |
| 31 // MIME type for WebP images. | |
| 32 const char kWebPMimeType[] = "image/webp"; | |
| 33 | |
| 34 NSURLResponse* NewImageResponse(NSURLResponse* webp_response, | |
| 35 size_t content_length, | |
| 36 WebpDecoder::DecodedImageFormat format) { | |
| 37 DCHECK(webp_response); | |
| 38 | |
| 39 NSString* mime_type = nil; | |
| 40 switch (format) { | |
| 41 case WebpDecoder::JPEG: | |
| 42 mime_type = @"image/jpeg"; | |
| 43 break; | |
| 44 case WebpDecoder::PNG: | |
| 45 mime_type = @"image/png"; | |
| 46 break; | |
| 47 case WebpDecoder::TIFF: | |
| 48 mime_type = @"image/tiff"; | |
| 49 break; | |
| 50 case WebpDecoder::DECODED_FORMAT_COUNT: | |
| 51 NOTREACHED(); | |
| 52 break; | |
| 53 } | |
| 54 DCHECK(mime_type); | |
| 55 | |
| 56 if ([webp_response isKindOfClass:[NSHTTPURLResponse class]]) { | |
| 57 NSHTTPURLResponse* http_response = | |
| 58 static_cast<NSHTTPURLResponse*>(webp_response); | |
| 59 NSMutableDictionary* header_fields = [NSMutableDictionary | |
| 60 dictionaryWithDictionary:[http_response allHeaderFields]]; | |
| 61 [header_fields setObject:[NSString stringWithFormat:@"%zu", content_length] | |
| 62 forKey:@"Content-Length"]; | |
| 63 [header_fields setObject:mime_type forKey:@"Content-Type"]; | |
| 64 return [[NSHTTPURLResponse alloc] initWithURL:[http_response URL] | |
| 65 statusCode:[http_response statusCode] | |
| 66 HTTPVersion:@"HTTP/1.1" | |
| 67 headerFields:header_fields]; | |
| 68 } else { | |
| 69 return [[NSURLResponse alloc] initWithURL:[webp_response URL] | |
| 70 MIMEType:mime_type | |
| 71 expectedContentLength:content_length | |
| 72 textEncodingName:[webp_response textEncodingName]]; | |
| 73 } | |
| 74 } | |
| 75 | |
| 76 class WebpDecoderDelegate : public WebpDecoder::Delegate { | |
| 77 public: | |
| 78 WebpDecoderDelegate(id<CRNNetworkClientProtocol> client, | |
| 79 const base::Time& request_creation_time, | |
| 80 const scoped_refptr<base::TaskRunner>& callback_runner) | |
| 81 : underlying_client_([client retain]), | |
| 82 callback_task_runner_(callback_runner), | |
| 83 request_creation_time_(request_creation_time) { | |
| 84 DCHECK(underlying_client_.get()); | |
| 85 } | |
| 86 | |
| 87 void SetOriginalResponse( | |
| 88 const base::scoped_nsobject<NSURLResponse>& response) { | |
| 89 original_response_.reset([response retain]); | |
| 90 } | |
| 91 | |
| 92 // WebpDecoder::Delegate methods. | |
| 93 void OnFinishedDecoding(bool success) override { | |
| 94 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( | |
| 95 [underlying_client_ retain]); | |
| 96 if (success) { | |
| 97 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ | |
| 98 [block_client didFinishLoading]; | |
| 99 })); | |
| 100 } else { | |
| 101 DLOG(WARNING) << "WebP decoding failed " | |
| 102 << base::SysNSStringToUTF8( | |
| 103 [[original_response_ URL] absoluteString]); | |
| 104 void (^errorBlock)(void) = ^{ | |
| 105 [block_client didFailWithNSErrorCode:NSURLErrorCannotDecodeContentData | |
| 106 netErrorCode:net::ERR_CONTENT_DECODING_FAILED]; | |
| 107 }; | |
| 108 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(errorBlock)); | |
| 109 } | |
| 110 } | |
| 111 | |
| 112 void SetImageFeatures(size_t total_size, | |
| 113 WebpDecoder::DecodedImageFormat format) override { | |
| 114 base::scoped_nsobject<NSURLResponse> imageResponse( | |
| 115 NewImageResponse(original_response_, total_size, format)); | |
| 116 DCHECK(imageResponse); | |
| 117 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( | |
| 118 [underlying_client_ retain]); | |
| 119 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ | |
| 120 [block_client didReceiveResponse:imageResponse]; | |
| 121 })); | |
| 122 } | |
| 123 | |
| 124 void OnDataDecoded(NSData* data) override { | |
| 125 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( | |
| 126 [underlying_client_ retain]); | |
| 127 callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ | |
| 128 [block_client didLoadData:data]; | |
| 129 })); | |
| 130 } | |
| 131 | |
| 132 private: | |
| 133 ~WebpDecoderDelegate() override {} | |
| 134 | |
| 135 base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> underlying_client_; | |
| 136 base::scoped_nsobject<NSURLResponse> original_response_; | |
| 137 scoped_refptr<base::TaskRunner> callback_task_runner_; | |
| 138 base::Time request_creation_time_; | |
| 139 }; | |
| 140 | |
| 141 } // namespace | |
| 142 | |
| 143 @interface WebPNetworkClient () { | |
| 144 scoped_refptr<webp_transcode::WebpDecoder> _webpDecoder; | |
| 145 scoped_refptr<WebpDecoderDelegate> _webpDecoderDelegate; | |
| 146 scoped_refptr<base::SequencedTaskRunner> _taskRunner; | |
| 147 base::Time _requestCreationTime; | |
| 148 } | |
| 149 @end | |
| 150 | |
| 151 @implementation WebPNetworkClient | |
| 152 | |
| 153 - (instancetype)init { | |
| 154 NOTREACHED() << "Use |-initWithTaskRunner:| instead"; | |
| 155 return nil; | |
| 156 } | |
| 157 | |
| 158 - (instancetype)initWithTaskRunner: | |
| 159 (const scoped_refptr<base::SequencedTaskRunner>&)runner { | |
| 160 if (self = [super init]) { | |
| 161 DCHECK(runner); | |
| 162 _taskRunner = runner; | |
| 163 } | |
| 164 return self; | |
| 165 } | |
| 166 | |
| 167 - (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest { | |
| 168 // Append 'image/webp' to the outgoing 'Accept' header. | |
| 169 const net::HttpRequestHeaders& headers = | |
| 170 nativeRequest->extra_request_headers(); | |
| 171 std::string acceptHeader; | |
| 172 if (headers.GetHeader("Accept", &acceptHeader)) { | |
| 173 // Add 'image/webp' if it isn't in the Accept header yet. | |
| 174 if (acceptHeader.find(kWebPMimeType) == std::string::npos) { | |
| 175 acceptHeader += std::string(",") + kWebPMimeType; | |
| 176 nativeRequest->SetExtraRequestHeaderByName("Accept", acceptHeader, true); | |
| 177 } | |
| 178 } else { | |
| 179 // All requests should already have an Accept: header, so this case | |
| 180 // should never happen outside of unit tests. | |
| 181 nativeRequest->SetExtraRequestHeaderByName("Accept", kWebPMimeType, false); | |
| 182 } | |
| 183 [super didCreateNativeRequest:nativeRequest]; | |
| 184 } | |
| 185 | |
| 186 - (void)didLoadData:(NSData*)data { | |
| 187 if (_webpDecoder.get()) { | |
| 188 // |data| is assumed to be immutable. | |
| 189 base::scoped_nsobject<NSData> scopedData([data retain]); | |
| 190 _taskRunner->PostTask(FROM_HERE, base::Bind(&WebpDecoder::OnDataReceived, | |
| 191 _webpDecoder, scopedData)); | |
| 192 } else { | |
| 193 [super didLoadData:data]; | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 - (void)didReceiveResponse:(NSURLResponse*)response { | |
| 198 DCHECK(self.underlyingClient); | |
| 199 NSString* responseMimeType = [response MIMEType]; | |
| 200 NSString* webPMimeType = | |
| 201 [NSString stringWithCString:kWebPMimeType encoding:NSASCIIStringEncoding]; | |
|
sdefresne
2015/03/20 11:06:40
nit: can we have a constant for this?
static cons
| |
| 202 if (responseMimeType && | |
| 203 [responseMimeType caseInsensitiveCompare:webPMimeType] == 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 } | |
| 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 |