summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordroger <droger@chromium.org>2015-03-20 09:44:51 -0700
committerCommit bot <commit-bot@chromium.org>2015-03-20 16:45:32 +0000
commitb04f6c5c7c6bee1a30494c3fc67bf3a8904fc129 (patch)
tree0696a919520e76072879a1ac519bc3bfbc88213f
parent82072cae99ed21403e9dd68fb8e6ffcd0d7b8992 (diff)
downloadchromium_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}
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/webp_transcode.gypi5
-rw-r--r--components/webp_transcode/DEPS2
-rw-r--r--components/webp_transcode/webp_network_client.h24
-rw-r--r--components/webp_transcode/webp_network_client.mm231
-rw-r--r--components/webp_transcode/webp_network_client_factory.h27
-rw-r--r--components/webp_transcode/webp_network_client_factory.mm42
-rw-r--r--components/webp_transcode/webp_network_client_unittest.mm73
8 files changed, 405 insertions, 0 deletions
diff --git a/components/components_tests.gyp b/components/components_tests.gyp
index 803e944..a383144 100644
--- a/components/components_tests.gyp
+++ b/components/components_tests.gyp
@@ -805,6 +805,7 @@
'sources': [
'open_from_clipboard/clipboard_recent_content_ios_unittest.mm',
'webp_transcode/webp_decoder_unittest.mm',
+ 'webp_transcode/webp_network_client_unittest.mm',
],
'sources!': [
'metrics/gpu/gpu_metrics_provider_unittest.cc',
diff --git a/components/webp_transcode.gypi b/components/webp_transcode.gypi
index b99003c..0658bf2 100644
--- a/components/webp_transcode.gypi
+++ b/components/webp_transcode.gypi
@@ -9,6 +9,7 @@
'type': 'static_library',
'dependencies': [
'../base/base.gyp:base',
+ '../ios/net/ios_net.gyp:ios_net',
'../net/net.gyp:net',
'../third_party/libwebp/libwebp.gyp:libwebp_dec',
],
@@ -18,6 +19,10 @@
'sources': [
'webp_transcode/webp_decoder.h',
'webp_transcode/webp_decoder.mm',
+ 'webp_transcode/webp_network_client.h',
+ 'webp_transcode/webp_network_client.mm',
+ 'webp_transcode/webp_network_client_factory.h',
+ 'webp_transcode/webp_network_client_factory.mm',
],
},
],
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);
+ }
+}