Commit b04f6c5c authored by droger's avatar droger Committed by Commit bot

[iOS] Upstream WebP network client

Review URL: https://codereview.chromium.org/1022813002

Cr-Commit-Position: refs/heads/master@{#321578}
parent 82072cae
......@@ -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',
......
......@@ -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',
],
},
],
......
......@@ -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",
]
// 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_
// 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
// 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_
// 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
// 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);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment