| 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 NSString* const kNSWebPMimeType = @"image/webp"; |
| 34 |
| 35 NSURLResponse* NewImageResponse(NSURLResponse* webp_response, |
| 36 size_t content_length, |
| 37 WebpDecoder::DecodedImageFormat format) { |
| 38 DCHECK(webp_response); |
| 39 |
| 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); |
| 56 |
| 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 } |
| 75 } |
| 76 |
| 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 } |
| 87 |
| 88 void SetOriginalResponse( |
| 89 const base::scoped_nsobject<NSURLResponse>& response) { |
| 90 original_response_.reset([response retain]); |
| 91 } |
| 92 |
| 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 } |
| 112 |
| 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 } |
| 124 |
| 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 } |
| 132 |
| 133 private: |
| 134 ~WebpDecoderDelegate() override {} |
| 135 |
| 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_; |
| 140 }; |
| 141 |
| 142 } // namespace |
| 143 |
| 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; |
| 149 } |
| 150 @end |
| 151 |
| 152 @implementation WebPNetworkClient |
| 153 |
| 154 - (instancetype)init { |
| 155 NOTREACHED() << "Use |-initWithTaskRunner:| instead"; |
| 156 return nil; |
| 157 } |
| 158 |
| 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; |
| 166 } |
| 167 |
| 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]; |
| 185 } |
| 186 |
| 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 } |
| 196 } |
| 197 |
| 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 } |
| 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 |