diff options
author | droger <droger@chromium.org> | 2015-03-20 09:44:51 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-03-20 16:45:32 +0000 |
commit | b04f6c5c7c6bee1a30494c3fc67bf3a8904fc129 (patch) | |
tree | 0696a919520e76072879a1ac519bc3bfbc88213f /components/webp_transcode | |
parent | 82072cae99ed21403e9dd68fb8e6ffcd0d7b8992 (diff) | |
download | chromium_src-b04f6c5c7c6bee1a30494c3fc67bf3a8904fc129.zip chromium_src-b04f6c5c7c6bee1a30494c3fc67bf3a8904fc129.tar.gz chromium_src-b04f6c5c7c6bee1a30494c3fc67bf3a8904fc129.tar.bz2 |
[iOS] Upstream WebP network client
Review URL: https://codereview.chromium.org/1022813002
Cr-Commit-Position: refs/heads/master@{#321578}
Diffstat (limited to 'components/webp_transcode')
-rw-r--r-- | components/webp_transcode/DEPS | 2 | ||||
-rw-r--r-- | components/webp_transcode/webp_network_client.h | 24 | ||||
-rw-r--r-- | components/webp_transcode/webp_network_client.mm | 231 | ||||
-rw-r--r-- | components/webp_transcode/webp_network_client_factory.h | 27 | ||||
-rw-r--r-- | components/webp_transcode/webp_network_client_factory.mm | 42 | ||||
-rw-r--r-- | components/webp_transcode/webp_network_client_unittest.mm | 73 |
6 files changed, 399 insertions, 0 deletions
diff --git a/components/webp_transcode/DEPS b/components/webp_transcode/DEPS index 0e26ac6..1e71d47 100644 --- a/components/webp_transcode/DEPS +++ b/components/webp_transcode/DEPS @@ -2,9 +2,11 @@ include_rules = [ "+net", # Only WebP decoding is allowed (no encoding). "+third_party/libwebp/webp/decode.h", + "+third_party/ocmock", # webp_transcode should not depend on //ios for library size reasons. "-ios", + "+ios/net", # webp_transcode is only used by iOS. "-content", ] diff --git a/components/webp_transcode/webp_network_client.h b/components/webp_transcode/webp_network_client.h new file mode 100644 index 0000000..307856b --- /dev/null +++ b/components/webp_transcode/webp_network_client.h @@ -0,0 +1,24 @@ +// 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. + +#ifndef COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_H_ +#define COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_H_ + +#import <Foundation/Foundation.h> + +#include "base/memory/ref_counted.h" +#import "ios/net/clients/crn_forwarding_network_client.h" +#import "ios/net/clients/crn_forwarding_network_client_factory.h" + +// Network client that decodes WebP images. +@interface WebPNetworkClient : CRNForwardingNetworkClient + +// |runner| is the task runner used to perform the image decoding. +// Designated initializer. +- (instancetype)initWithTaskRunner: + (const scoped_refptr<base::SequencedTaskRunner>&)runner; + +@end + +#endif // COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_H_ 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 diff --git a/components/webp_transcode/webp_network_client_factory.h b/components/webp_transcode/webp_network_client_factory.h new file mode 100644 index 0000000..d64f59d --- /dev/null +++ b/components/webp_transcode/webp_network_client_factory.h @@ -0,0 +1,27 @@ +// 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. + +#ifndef COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_FACTORY_H_ +#define COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_FACTORY_H_ + +#import <Foundation/Foundation.h> + +#include "base/memory/ref_counted.h" +#import "ios/net/clients/crn_forwarding_network_client_factory.h" + +namespace base { +class SequencedTaskRunner; +} + +// Factory that creates WebPNetworkClient instances. +@interface WebPNetworkClientFactory : CRNForwardingNetworkClientFactory + +// |runner| is the task runner used to perform the image decoding. +// Designated initializer. +- (instancetype)initWithTaskRunner: + (const scoped_refptr<base::SequencedTaskRunner>&)runner; + +@end + +#endif // COMPONENTS_WEBP_TRANSCODE_WEBP_NETWORK_CLIENT_FACTORY_H_ diff --git a/components/webp_transcode/webp_network_client_factory.mm b/components/webp_transcode/webp_network_client_factory.mm new file mode 100644 index 0000000..4c8bd0f --- /dev/null +++ b/components/webp_transcode/webp_network_client_factory.mm @@ -0,0 +1,42 @@ +// 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_factory.h" + +#include "base/logging.h" +#include "base/sequenced_task_runner.h" +#include "base/single_thread_task_runner.h" +#import "components/webp_transcode/webp_network_client.h" + +@interface WebPNetworkClientFactory () { + scoped_refptr<base::SequencedTaskRunner> _taskRunner; +} +@end + +@implementation WebPNetworkClientFactory + +- (instancetype)init { + NOTREACHED() << "Use |-initWithTaskRunner:| instead"; + return nil; +} + +- (Class)clientClass { + return [WebPNetworkClient class]; +} + +- (instancetype)initWithTaskRunner: + (const scoped_refptr<base::SequencedTaskRunner>&)runner { + if ((self = [super init])) { + DCHECK(runner); + _taskRunner = runner; + } + return self; +} + +- (CRNForwardingNetworkClient*)clientHandlingAnyRequest { + return + [[[WebPNetworkClient alloc] initWithTaskRunner:_taskRunner] autorelease]; +} + +@end diff --git a/components/webp_transcode/webp_network_client_unittest.mm b/components/webp_transcode/webp_network_client_unittest.mm new file mode 100644 index 0000000..da0ec0b --- /dev/null +++ b/components/webp_transcode/webp_network_client_unittest.mm @@ -0,0 +1,73 @@ +// Copyright 2014 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. + +#include "components/webp_transcode/webp_network_client.h" + +#include "base/mac/scoped_nsobject.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/thread_task_runner_handle.h" +#include "net/http/http_request_headers.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" +#import "third_party/ocmock/OCMock/OCMock.h" + +namespace { +class WebPNetworkClientTest : public testing::Test { + public: + WebPNetworkClientTest() { + // Set up mock original network client proxy. + OCMockObject* mockProxy_ = [[OCMockObject + niceMockForProtocol:@protocol(CRNNetworkClientProtocol)] retain]; + mockWebProxy_.reset(mockProxy_); + + // Link all the mock objects into the WebPNetworkClient. + webp_client_.reset([[WebPNetworkClient alloc] + initWithTaskRunner:base::ThreadTaskRunnerHandle::Get()]); + [webp_client_ + setUnderlyingClient:(id<CRNNetworkClientProtocol>)mockWebProxy_]; + } + + protected: + base::MessageLoop loop_; + base::scoped_nsobject<WebPNetworkClient> webp_client_; + // Holds a mock CRNNetworkClientProtocol object. + base::scoped_nsobject<OCMockObject> mockWebProxy_; +}; +} // namespace + +TEST_F(WebPNetworkClientTest, TestAcceptHeaders) { + const struct { + const std::string header_in; + const std::string header_out; + } tests[] = { + {"", "image/webp"}, + {"*/*", "*/*,image/webp"}, + {"image/webp", "image/webp"}, + {"text/html,*/*", "text/html,*/*,image/webp"}, + // Desktop Chrome default without image/webp. + {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8," + "image/webp"}, + // Desktop Chrome default. + {"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp," + "*/*;q=0.8", + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp," + "*/*;q=0.8"}}; + GURL url("http://www.google.com"); + scoped_ptr<net::URLRequestContext> request_context( + new net::TestURLRequestContext(false)); + for (size_t i = 0; i < arraysize(tests); ++i) { + scoped_ptr<net::URLRequest> request = + request_context->CreateRequest(url, net::DEFAULT_PRIORITY, nullptr, + nullptr).Pass(); + if (!tests[i].header_in.empty()) + request->SetExtraRequestHeaderByName("Accept", tests[i].header_in, true); + [webp_client_ didCreateNativeRequest:request.get()]; + const net::HttpRequestHeaders& headers = request->extra_request_headers(); + std::string acceptHeader; + EXPECT_TRUE(headers.GetHeader("Accept", &acceptHeader)); + EXPECT_EQ(tests[i].header_out, acceptHeader); + } +} |