| Index: components/webp_transcode/webp_network_client.mm
|
| diff --git a/components/webp_transcode/webp_network_client.mm b/components/webp_transcode/webp_network_client.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..b39faa39626c85359733c1d081c257635154af1e
|
| --- /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
|
|
|