diff options
Diffstat (limited to 'components/webp_transcode/webp_network_client.mm')
-rw-r--r-- | components/webp_transcode/webp_network_client.mm | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/components/webp_transcode/webp_network_client.mm b/components/webp_transcode/webp_network_client.mm new file mode 100644 index 0000000..b39faa3 --- /dev/null +++ b/components/webp_transcode/webp_network_client.mm @@ -0,0 +1,231 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "components/webp_transcode/webp_network_client.h" + +#include "base/bind.h" +#include "base/compiler_specific.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/mac/bind_objc_block.h" +#include "base/mac/scoped_nsobject.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#include "base/strings/string_util.h" +#include "base/strings/sys_string_conversions.h" +#include "base/thread_task_runner_handle.h" +#include "components/webp_transcode/webp_decoder.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request.h" + +namespace net { +class URLRequest; +} + +using namespace webp_transcode; + +namespace { + +// MIME type for WebP images. +const char kWebPMimeType[] = "image/webp"; +NSString* const kNSWebPMimeType = @"image/webp"; + +NSURLResponse* NewImageResponse(NSURLResponse* webp_response, + size_t content_length, + WebpDecoder::DecodedImageFormat format) { + DCHECK(webp_response); + + NSString* mime_type = nil; + switch (format) { + case WebpDecoder::JPEG: + mime_type = @"image/jpeg"; + break; + case WebpDecoder::PNG: + mime_type = @"image/png"; + break; + case WebpDecoder::TIFF: + mime_type = @"image/tiff"; + break; + case WebpDecoder::DECODED_FORMAT_COUNT: + NOTREACHED(); + break; + } + DCHECK(mime_type); + + if ([webp_response isKindOfClass:[NSHTTPURLResponse class]]) { + NSHTTPURLResponse* http_response = + static_cast<NSHTTPURLResponse*>(webp_response); + NSMutableDictionary* header_fields = [NSMutableDictionary + dictionaryWithDictionary:[http_response allHeaderFields]]; + [header_fields setObject:[NSString stringWithFormat:@"%zu", content_length] + forKey:@"Content-Length"]; + [header_fields setObject:mime_type forKey:@"Content-Type"]; + return [[NSHTTPURLResponse alloc] initWithURL:[http_response URL] + statusCode:[http_response statusCode] + HTTPVersion:@"HTTP/1.1" + headerFields:header_fields]; + } else { + return [[NSURLResponse alloc] initWithURL:[webp_response URL] + MIMEType:mime_type + expectedContentLength:content_length + textEncodingName:[webp_response textEncodingName]]; + } +} + +class WebpDecoderDelegate : public WebpDecoder::Delegate { + public: + WebpDecoderDelegate(id<CRNNetworkClientProtocol> client, + const base::Time& request_creation_time, + const scoped_refptr<base::TaskRunner>& callback_runner) + : underlying_client_([client retain]), + callback_task_runner_(callback_runner), + request_creation_time_(request_creation_time) { + DCHECK(underlying_client_.get()); + } + + void SetOriginalResponse( + const base::scoped_nsobject<NSURLResponse>& response) { + original_response_.reset([response retain]); + } + + // WebpDecoder::Delegate methods. + void OnFinishedDecoding(bool success) override { + base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( + [underlying_client_ retain]); + if (success) { + callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + [block_client didFinishLoading]; + })); + } else { + DLOG(WARNING) << "WebP decoding failed " + << base::SysNSStringToUTF8( + [[original_response_ URL] absoluteString]); + void (^errorBlock)(void) = ^{ + [block_client didFailWithNSErrorCode:NSURLErrorCannotDecodeContentData + netErrorCode:net::ERR_CONTENT_DECODING_FAILED]; + }; + callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(errorBlock)); + } + } + + void SetImageFeatures(size_t total_size, + WebpDecoder::DecodedImageFormat format) override { + base::scoped_nsobject<NSURLResponse> imageResponse( + NewImageResponse(original_response_, total_size, format)); + DCHECK(imageResponse); + base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( + [underlying_client_ retain]); + callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + [block_client didReceiveResponse:imageResponse]; + })); + } + + void OnDataDecoded(NSData* data) override { + base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> block_client( + [underlying_client_ retain]); + callback_task_runner_->PostTask(FROM_HERE, base::BindBlock(^{ + [block_client didLoadData:data]; + })); + } + + private: + ~WebpDecoderDelegate() override {} + + base::scoped_nsprotocol<id<CRNNetworkClientProtocol>> underlying_client_; + base::scoped_nsobject<NSURLResponse> original_response_; + scoped_refptr<base::TaskRunner> callback_task_runner_; + base::Time request_creation_time_; +}; + +} // namespace + +@interface WebPNetworkClient () { + scoped_refptr<webp_transcode::WebpDecoder> _webpDecoder; + scoped_refptr<WebpDecoderDelegate> _webpDecoderDelegate; + scoped_refptr<base::SequencedTaskRunner> _taskRunner; + base::Time _requestCreationTime; +} +@end + +@implementation WebPNetworkClient + +- (instancetype)init { + NOTREACHED() << "Use |-initWithTaskRunner:| instead"; + return nil; +} + +- (instancetype)initWithTaskRunner: + (const scoped_refptr<base::SequencedTaskRunner>&)runner { + if (self = [super init]) { + DCHECK(runner); + _taskRunner = runner; + } + return self; +} + +- (void)didCreateNativeRequest:(net::URLRequest*)nativeRequest { + // Append 'image/webp' to the outgoing 'Accept' header. + const net::HttpRequestHeaders& headers = + nativeRequest->extra_request_headers(); + std::string acceptHeader; + if (headers.GetHeader("Accept", &acceptHeader)) { + // Add 'image/webp' if it isn't in the Accept header yet. + if (acceptHeader.find(kWebPMimeType) == std::string::npos) { + acceptHeader += std::string(",") + kWebPMimeType; + nativeRequest->SetExtraRequestHeaderByName("Accept", acceptHeader, true); + } + } else { + // All requests should already have an Accept: header, so this case + // should never happen outside of unit tests. + nativeRequest->SetExtraRequestHeaderByName("Accept", kWebPMimeType, false); + } + [super didCreateNativeRequest:nativeRequest]; +} + +- (void)didLoadData:(NSData*)data { + if (_webpDecoder.get()) { + // |data| is assumed to be immutable. + base::scoped_nsobject<NSData> scopedData([data retain]); + _taskRunner->PostTask(FROM_HERE, base::Bind(&WebpDecoder::OnDataReceived, + _webpDecoder, scopedData)); + } else { + [super didLoadData:data]; + } +} + +- (void)didReceiveResponse:(NSURLResponse*)response { + DCHECK(self.underlyingClient); + NSString* responseMimeType = [response MIMEType]; + if (responseMimeType && + [responseMimeType caseInsensitiveCompare:kNSWebPMimeType] == + NSOrderedSame) { + _webpDecoderDelegate = + new WebpDecoderDelegate(self.underlyingClient, _requestCreationTime, + base::ThreadTaskRunnerHandle::Get()); + _webpDecoder = new webp_transcode::WebpDecoder(_webpDecoderDelegate.get()); + base::scoped_nsobject<NSURLResponse> scoped_response([response copy]); + _taskRunner->PostTask(FROM_HERE, + base::Bind(&WebpDecoderDelegate::SetOriginalResponse, + _webpDecoderDelegate, scoped_response)); + // Do not call super here, the WebpDecoderDelegate will update the mime type + // and call |-didReceiveResponse:|. + } else { + // If this isn't a WebP, pass the call up the chain. + // TODO(marq): It would be nice if at this point the client could remove + // itself from the client stack. + [super didReceiveResponse:response]; + } +} + +- (void)didFinishLoading { + if (_webpDecoder.get()) { + _taskRunner->PostTask(FROM_HERE, + base::Bind(&WebpDecoder::Stop, _webpDecoder)); + } else { + [super didFinishLoading]; + } +} + +@end |