Commit 499acc80 authored by Yuke Liao's avatar Yuke Liao Committed by Commit Bot

Revert "DownloadTaskImpl implementation for //ios/web Download API."

This reverts commit cef83520.

Reason for revert: DownloadTaskImplTest.* tests fail on iOS 11 devices, both iPhone and iPad.

Original change's description:
> DownloadTaskImpl implementation for //ios/web Download API.
> 
> This CL implements DownloadTask public interface.
> 
> DownloadControllerImpl CL: crrev.com/c/758525
> Design doc: http://go/ios-web-download-api
> 
> Bug: 780646
> Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
> Change-Id: I2c508e77f3e529223499d6f2791b28011589fe90
> Reviewed-on: https://chromium-review.googlesource.com/758506
> Reviewed-by: Sylvain Defresne <sdefresne@chromium.org>
> Reviewed-by: Gregory Chatzinoff <gchatz@chromium.org>
> Commit-Queue: Eugene But <eugenebut@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#517176}

TBR=sdefresne@chromium.org,eugenebut@chromium.org,gchatz@chromium.org

Change-Id: I14f0c37aa92330b0a665bf59bb665f010ee1209d
No-Presubmit: true
No-Tree-Checks: true
No-Try: true
Bug: 780646
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Reviewed-on: https://chromium-review.googlesource.com/776067Reviewed-by: default avatarYuke Liao <liaoyuke@chromium.org>
Commit-Queue: Yuke Liao <liaoyuke@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517225}
parent 5c3c4ac8
...@@ -40,7 +40,6 @@ source_set("web") { ...@@ -40,7 +40,6 @@ source_set("web") {
":navigation_resources", ":navigation_resources",
":resources", ":resources",
"//base", "//base",
"//ios/web/download",
"//ios/web/interstitials", "//ios/web/interstitials",
"//ios/web/navigation", "//ios/web/navigation",
"//ios/web/net", "//ios/web/net",
...@@ -167,7 +166,6 @@ test("ios_web_unittests") { ...@@ -167,7 +166,6 @@ test("ios_web_unittests") {
":ios_web_web_state_unittests", ":ios_web_web_state_unittests",
":ios_web_webui_unittests", ":ios_web_webui_unittests",
"//ios/testing:http_server_bundle_data", "//ios/testing:http_server_bundle_data",
"//ios/web/download:download_unittests",
] ]
assert_no_deps = ios_assert_no_deps assert_no_deps = ios_assert_no_deps
......
# Copyright 2017 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("//ios/build/config.gni")
source_set("download") {
deps = [
"//base",
"//ios/web/net/cookies",
"//ios/web/public",
"//ios/web/public/download",
"//ios/web/web_state:error_translation_util",
]
sources = [
"download_task_impl.h",
"download_task_impl.mm",
]
libs = [ "UIKit.framework" ]
configs += [ "//build/config/compiler:enable_arc" ]
}
source_set("download_unittests") {
configs += [ "//build/config/compiler:enable_arc" ]
testonly = true
deps = [
"//base",
"//ios/testing:ios_test_support",
"//ios/testing:ocmock_support",
"//ios/web/download",
"//ios/web/net/cookies",
"//ios/web/public/download",
"//ios/web/public/test",
"//ios/web/public/test/fakes",
"//ios/web/test/fakes",
"//net",
"//testing/gmock",
"//testing/gtest",
]
sources = [
"download_task_impl_unittest.mm",
]
}
eugenebut@chromium.org
gchatz@chromium.org
# TEAM: ios-directory-owners@chromium.org
# OS: iOS
// Copyright 2017 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 IOS_WEB_DOWNLOAD_DOWNLOAD_TASK_IMPL_H_
#define IOS_WEB_DOWNLOAD_DOWNLOAD_TASK_IMPL_H_
#include <string>
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#import "ios/web/public/download/download_task.h"
#include "url/gurl.h"
@class NSURLSession;
namespace net {
class URLFetcherResponseWriter;
}
namespace web {
class DownloadTaskObserver;
class WebState;
// Implements DownloadTask interface. Uses background NSURLSession as
// implementation.
class DownloadTaskImpl : public DownloadTask {
public:
class Delegate {
public:
// Called when download task is about to be destroyed. Delegate should
// remove all references to the given DownloadTask and stop using it.
virtual void OnTaskDestroyed(DownloadTaskImpl* task) = 0;
// Creates background NSURLSession with given |identifier|, |delegate| and
// |delegate_queue|.
virtual NSURLSession* CreateSession(NSString* identifier,
id<NSURLSessionDataDelegate> delegate,
NSOperationQueue* delegate_queue) = 0;
virtual ~Delegate() = default;
};
// Constructs a new DownloadTaskImpl objects. |web_state|, |identifier| and
// |delegate| must be valid.
DownloadTaskImpl(const WebState* web_state,
const GURL& original_url,
const std::string& content_disposition,
int64_t total_bytes,
const std::string& mime_type,
NSString* identifier,
Delegate* delegate);
// Stops the download operation and clears the delegate.
void ShutDown();
// DownloadTask overrides:
void Start(std::unique_ptr<net::URLFetcherResponseWriter> writer) override;
net::URLFetcherResponseWriter* GetResponseWriter() const override;
NSString* GetIndentifier() const override;
const GURL& GetOriginalUrl() const override;
bool IsDone() const override;
int GetErrorCode() const override;
int64_t GetTotalBytes() const override;
int GetPercentComplete() const override;
std::string GetContentDisposition() const override;
std::string GetMimeType() const override;
base::string16 GetSuggestedFilename() const override;
void AddObserver(DownloadTaskObserver* observer) override;
void RemoveObserver(DownloadTaskObserver* observer) override;
~DownloadTaskImpl() override;
private:
// Creates background NSURLSession with given |identifier|.
NSURLSession* CreateSession(NSString* identifier);
// Asynchronously returns cookies for WebState associated with this task (on
// iOS 10 and earlier, the array is always empty as it is not possible to
// access the cookies). Must be called on UI thread. The callback will be
// invoked on the UI thread.
void GetCookies(base::Callback<void(NSArray<NSHTTPCookie*>*)> callback);
// Asynchronously returns cookies for WebState associated with this task. Must
// be called on UI thread. The callback will be invoked on the UI thread.
void GetWKCookies(base::Callback<void(NSArray<NSHTTPCookie*>*)> callback)
API_AVAILABLE(ios(11.0));
// Starts the download with given cookies.
void StartWithCookies(NSArray<NSHTTPCookie*>* cookies);
// Called when download task was updated.
void OnDownloadUpdated();
// Called when download was completed and the data writing was finished.
void OnDownloadFinished(int error_code);
// A list of observers. Weak references.
base::ObserverList<DownloadTaskObserver, true> observers_;
// Back up corresponding public methods of DownloadTask interface.
std::unique_ptr<net::URLFetcherResponseWriter> writer_;
GURL original_url_;
bool is_done_ = false;
int error_code_ = 0;
int64_t total_bytes_ = -1;
int percent_complete_ = -1;
std::string content_disposition_;
std::string mime_type_;
const WebState* web_state_ = nullptr;
Delegate* delegate_ = nullptr;
NSURLSession* session_ = nil;
NSURLSessionTask* session_task_ = nil;
base::WeakPtrFactory<DownloadTaskImpl> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(DownloadTaskImpl);
};
} // namespace web
#endif // IOS_WEB_DOWNLOAD_DOWNLOAD_TASK_IMPL_H_
// Copyright 2017 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 "ios/web/download/download_task_impl.h"
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#import "base/mac/bind_objc_block.h"
#import "ios/web/net/cookies/wk_cookie_util.h"
#include "ios/web/public/browser_state.h"
#import "ios/web/public/download/download_task_observer.h"
#import "ios/web/public/web_state/web_state.h"
#include "ios/web/public/web_thread.h"
#import "ios/web/web_state/error_translation_util.h"
#include "net/base/filename_util.h"
#include "net/base/io_buffer.h"
#import "net/base/mac/url_conversions.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_fetcher_response_writer.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// CRWURLSessionDelegate block types.
using PropertiesBlock = void (^)(NSURLSessionTask*, NSError*);
using DataBlock = void (^)(scoped_refptr<net::IOBufferWithSize> buffer);
// Translates an CFNetwork error code to a net error code. Returns 0 if |error|
// is nil.
int GetNetErrorCodeFromNSError(NSError* error) {
int error_code = 0;
if (error) {
if (!web::GetNetErrorFromIOSErrorCode(error.code, &error_code)) {
error_code = net::ERR_FAILED;
}
}
return error_code;
}
// Creates a new buffer from raw |data| and |size|.
scoped_refptr<net::IOBufferWithSize> GetBuffer(const void* data, size_t size) {
auto buffer = base::MakeRefCounted<net::IOBufferWithSize>(size);
memcpy(buffer->data(), data, size);
return buffer;
}
// Percent complete for the given NSURLSessionTask within [0..100] range.
int GetTaskPercentComplete(NSURLSessionTask* task) {
DCHECK(task);
if (!task.countOfBytesExpectedToReceive) {
return 100;
}
DCHECK_GE(task.countOfBytesExpectedToReceive, task.countOfBytesReceived);
return 100.0 * task.countOfBytesReceived / task.countOfBytesExpectedToReceive;
}
// Used as no-op callback.
void DoNothing(int) {}
} // namespace
// NSURLSessionDataDelegate that forwards data and properties task updates to
// the client. Client of this delegate can pass blocks to receive the updates.
@interface CRWURLSessionDelegate : NSObject<NSURLSessionDataDelegate>
- (instancetype)init NS_UNAVAILABLE;
// Initializes delegate with blocks. |propertiesBlock| is called when
// DownloadTaskImpl should update its properties (is_done, error_code,
// total_bytes, and percent_complete) and call OnDownloadUpdated callback.
// |dataBlock| is called when DownloadTaskImpl should write a chunk of
// downloaded data.
- (instancetype)initWithPropertiesBlock:(PropertiesBlock)propertiesBlock
dataBlock:(DataBlock)dataBlock
NS_DESIGNATED_INITIALIZER;
@end
@implementation CRWURLSessionDelegate {
PropertiesBlock _propertiesBlock;
DataBlock _dataBlock;
}
- (instancetype)initWithPropertiesBlock:(PropertiesBlock)propertiesBlock
dataBlock:(DataBlock)dataBlock {
DCHECK(propertiesBlock);
DCHECK(dataBlock);
if ((self = [super init])) {
_propertiesBlock = propertiesBlock;
_dataBlock = dataBlock;
}
return self;
}
- (void)URLSession:(NSURLSession*)session
task:(NSURLSessionTask*)task
didCompleteWithError:(nullable NSError*)error {
_propertiesBlock(task, error);
}
- (void)URLSession:(NSURLSession*)session
dataTask:(NSURLSessionDataTask*)task
didReceiveData:(NSData*)data {
using Bytes = const void* _Nonnull;
[data enumerateByteRangesUsingBlock:^(Bytes bytes, NSRange range, BOOL*) {
auto buffer = GetBuffer(bytes, range.length);
_dataBlock(std::move(buffer));
}];
_propertiesBlock(task, nil);
}
- (void)URLSession:(NSURLSession*)session
didReceiveChallenge:(NSURLAuthenticationChallenge*)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition,
NSURLCredential* _Nullable))handler {
// TODO(crbug.com/780911): use CRWCertVerificationController to get
// CertAcceptPolicy for this |challenge|.
handler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
@end
namespace web {
DownloadTaskImpl::DownloadTaskImpl(const WebState* web_state,
const GURL& original_url,
const std::string& content_disposition,
int64_t total_bytes,
const std::string& mime_type,
NSString* identifier,
Delegate* delegate)
: original_url_(original_url),
total_bytes_(total_bytes),
content_disposition_(content_disposition),
mime_type_(mime_type),
web_state_(web_state),
delegate_(delegate),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
session_ = CreateSession(identifier);
DCHECK(web_state_);
DCHECK(delegate_);
DCHECK(session_);
}
DownloadTaskImpl::~DownloadTaskImpl() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
if (delegate_) {
delegate_->OnTaskDestroyed(this);
}
ShutDown();
}
void DownloadTaskImpl::ShutDown() {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
[session_task_ cancel];
session_task_ = nil;
delegate_ = nullptr;
}
void DownloadTaskImpl::Start(
std::unique_ptr<net::URLFetcherResponseWriter> writer) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
DCHECK(!writer_);
writer_ = std::move(writer);
GetCookies(base::Bind(&DownloadTaskImpl::StartWithCookies,
weak_factory_.GetWeakPtr()));
}
net::URLFetcherResponseWriter* DownloadTaskImpl::GetResponseWriter() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return writer_.get();
}
NSString* DownloadTaskImpl::GetIndentifier() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return session_.configuration.identifier;
}
const GURL& DownloadTaskImpl::GetOriginalUrl() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return original_url_;
}
bool DownloadTaskImpl::IsDone() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return is_done_;
}
int DownloadTaskImpl::GetErrorCode() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return error_code_;
}
int64_t DownloadTaskImpl::GetTotalBytes() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return total_bytes_;
}
int DownloadTaskImpl::GetPercentComplete() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return percent_complete_;
}
std::string DownloadTaskImpl::GetContentDisposition() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return content_disposition_;
}
std::string DownloadTaskImpl::GetMimeType() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return mime_type_;
}
base::string16 DownloadTaskImpl::GetSuggestedFilename() const {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
return net::GetSuggestedFilename(GetOriginalUrl(), GetContentDisposition(),
/*referrer_charset=*/std::string(),
/*suggested_name=*/std::string(),
/*mime_type=*/std::string(),
/*default_name=*/"document");
}
void DownloadTaskImpl::AddObserver(DownloadTaskObserver* observer) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
DCHECK(!observers_.HasObserver(observer));
observers_.AddObserver(observer);
}
void DownloadTaskImpl::RemoveObserver(DownloadTaskObserver* observer) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
DCHECK(observers_.HasObserver(observer));
observers_.RemoveObserver(observer);
}
NSURLSession* DownloadTaskImpl::CreateSession(NSString* identifier) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
DCHECK(identifier.length);
base::WeakPtr<DownloadTaskImpl> weak_this = weak_factory_.GetWeakPtr();
id<NSURLSessionDataDelegate> session_delegate = [[CRWURLSessionDelegate alloc]
initWithPropertiesBlock:^(NSURLSessionTask* task, NSError* error) {
if (!weak_this.get()) {
return;
}
error_code_ = GetNetErrorCodeFromNSError(error);
percent_complete_ = GetTaskPercentComplete(task);
total_bytes_ = task.countOfBytesExpectedToReceive;
if (task.state != NSURLSessionTaskStateCompleted) {
OnDownloadUpdated();
// Download is still in progress, nothing to do here.
return;
}
// Download has finished, so finalize the writer and signal completion.
auto callback = base::Bind(&DownloadTaskImpl::OnDownloadFinished,
weak_factory_.GetWeakPtr());
if (writer_->Finish(error_code_, callback) != net::ERR_IO_PENDING) {
OnDownloadFinished(error_code_);
}
}
dataBlock:^(scoped_refptr<net::IOBufferWithSize> buffer) {
if (weak_this.get()) {
// Ignore Write's completion handler. |dataBlock| may be called
// multiple times for a single downloaded chunk of data and there is
// no need to wait for writing completion.
writer_->Write(buffer.get(), buffer->size(), base::Bind(&DoNothing));
}
}];
return delegate_->CreateSession(identifier, session_delegate,
NSOperationQueue.currentQueue);
}
void DownloadTaskImpl::GetCookies(
base::Callback<void(NSArray<NSHTTPCookie*>*)> callback) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
if (@available(iOS 11, *)) {
GetWKCookies(callback);
} else {
WebThread::PostTask(WebThread::UI, FROM_HERE, base::BindBlockArc(^{
callback.Run([NSArray array]);
}));
}
}
void DownloadTaskImpl::GetWKCookies(
base::Callback<void(NSArray<NSHTTPCookie*>*)> callback) {
DCHECK_CURRENTLY_ON(WebThread::UI);
auto store = WKCookieStoreForBrowserState(web_state_->GetBrowserState());
DCHECK(store);
WebThread::PostTask(WebThread::IO, FROM_HERE, base::BindBlockArc(^{
[store getAllCookies:^(NSArray<NSHTTPCookie*>* cookies) {
// getAllCookies: callback is always called on UI thread.
DCHECK_CURRENTLY_ON(WebThread::UI);
callback.Run(cookies);
}];
}));
}
void DownloadTaskImpl::StartWithCookies(NSArray<NSHTTPCookie*>* cookies) {
DCHECK_CURRENTLY_ON(web::WebThread::UI);
DCHECK(writer_);
NSURL* url = net::NSURLWithGURL(GetOriginalUrl());
session_task_ = [session_ dataTaskWithURL:url];
[session_.configuration.HTTPCookieStorage storeCookies:cookies
forTask:session_task_];
[session_task_ resume];
OnDownloadUpdated();
}
void DownloadTaskImpl::OnDownloadUpdated() {
for (auto& observer : observers_)
observer.OnDownloadUpdated(this);
}
void DownloadTaskImpl::OnDownloadFinished(int error_code) {
is_done_ = true;
session_task_ = nil;
OnDownloadUpdated();
}
} // namespace web
// Copyright 2017 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 "ios/web/download/download_task_impl.h"
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#include <memory>
#include "base/compiler_specific.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#import "base/mac/bind_objc_block.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#import "ios/testing/wait_util.h"
#import "ios/web/net/cookies/wk_cookie_util.h"
#import "ios/web/public/download/download_task_observer.h"
#import "ios/web/public/test/fakes/test_web_state.h"
#include "ios/web/public/test/web_test.h"
#import "ios/web/test/fakes/crw_fake_nsurl_session_task.h"
#include "net/base/net_errors.h"
#include "net/url_request/url_fetcher_response_writer.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/gtest_mac.h"
#import "third_party/ocmock/OCMock/OCMock.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using testing::kWaitForDownloadTimeout;
using testing::kWaitForFileOperationTimeout;
using testing::WaitUntilConditionOrTimeout;
using base::BindBlockArc;
namespace web {
namespace {
const char kUrl[] = "chromium://download.test/";
const char kContentDisposition[] = "attachment; filename=file.test";
const char kMimeType[] = "application/pdf";
class MockDownloadTaskObserver : public DownloadTaskObserver {
public:
MOCK_METHOD1(OnDownloadUpdated, void(const DownloadTask* task));
};
// Mocks DownloadTaskImpl::Delegate's OnTaskUpdated and OnTaskDestroyed methods
// and stubs DownloadTaskImpl::Delegate::CreateSession with session mock.
class FakeDownloadTaskImplDelegate : public DownloadTaskImpl::Delegate {
public:
FakeDownloadTaskImplDelegate()
: configuration_([NSURLSessionConfiguration
backgroundSessionConfigurationWithIdentifier:
[NSUUID UUID].UUIDString]) {}
MOCK_METHOD1(OnTaskDestroyed, void(DownloadTaskImpl* task));
// Returns mock, which can be accessed via session() method.
NSURLSession* CreateSession(NSString* identifier,
id<NSURLSessionDataDelegate> delegate,
NSOperationQueue* delegate_queue) {
// Make sure this method is called only once.
EXPECT_FALSE(session_);
EXPECT_FALSE(session_delegate_);
session_delegate_ = delegate;
session_ = OCMStrictClassMock([NSURLSession class]);
OCMStub([session_ configuration]).andReturn(configuration_);
return session_;
}
// These methods return session objects injected into DownloadTaskImpl.
NSURLSessionConfiguration* configuration() { return configuration_; }
id session() { return session_; }
id<NSURLSessionDataDelegate> session_delegate() { return session_delegate_; }
private:
id<NSURLSessionDataDelegate> session_delegate_;
id configuration_;
id session_;
};
} // namespace
// Test fixture for testing DownloadTaskImplTest class.
class DownloadTaskImplTest : public WebTest {
protected:
DownloadTaskImplTest()
: task_(std::make_unique<DownloadTaskImpl>(
&web_state_,
GURL(kUrl),
kContentDisposition,
/*total_bytes=*/-1,
kMimeType,
task_delegate_.configuration().identifier,
&task_delegate_)) {
web_state_.SetBrowserState(GetBrowserState());
task_->AddObserver(&task_observer_);
}
~DownloadTaskImplTest() {
if (task_) {
task_->RemoveObserver(&task_observer_);
}
}
// Starts the download and return NSURLSessionDataTask fake for this task.
CRWFakeNSURLSessionTask* Start(
std::unique_ptr<net::URLFetcherResponseWriter> writer) {
// Inject fake NSURLSessionDataTask into DownloadTaskImpl.
NSURL* url = [NSURL URLWithString:@(kUrl)];
CRWFakeNSURLSessionTask* session_task =
[[CRWFakeNSURLSessionTask alloc] initWithURL:url];
OCMExpect([task_delegate_.session() dataTaskWithURL:url])
.andReturn(session_task);
// Start the download.
task_->Start(std::move(writer));
bool success = WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
base::RunLoop().RunUntilIdle();
return session_task.state == NSURLSessionTaskStateRunning;
});
return success ? session_task : nil;
}
// Starts the download and return NSURLSessionDataTask fake for this task.
// Same as above, but uses URLFetcherStringWriter as response writer.
CRWFakeNSURLSessionTask* Start() {
return Start(std::make_unique<net::URLFetcherStringWriter>());
}
// Sets cookie for the test browser state.
bool SetCookie(NSHTTPCookie* cookie) WARN_UNUSED_RESULT
API_AVAILABLE(ios(11.0)) {
auto store = web::WKCookieStoreForBrowserState(GetBrowserState());
__block bool cookie_was_set = false;
[store setCookie:cookie
completionHandler:^{
cookie_was_set = true;
}];
return WaitUntilConditionOrTimeout(testing::kWaitForCookiesTimeout, ^{
return cookie_was_set;
});
}
// Session and session delegate injected into DownloadTaskImpl for testing.
NSURLSession* session() { return task_delegate_.session(); }
id<NSURLSessionDataDelegate> session_delegate() {
return task_delegate_.session_delegate();
}
// Updates NSURLSessionTask.countOfBytesReceived and calls
// URLSession:dataTask:didReceiveData: callback. |data_str| is null terminated
// C-string that represents the downloaded data.
void SimulateDataDownload(CRWFakeNSURLSessionTask* session_task,
const char data_str[]) {
session_task.countOfBytesReceived += strlen(data_str);
NSData* data = [NSData dataWithBytes:data_str length:strlen(data_str)];
[session_delegate() URLSession:session()
dataTask:session_task
didReceiveData:data];
}
// Sets NSURLSessionTask.state to NSURLSessionTaskStateCompleted and calls
// URLSession:dataTask:didCompleteWithError: callback.
void SimulateDownloadCompletion(CRWFakeNSURLSessionTask* session_task,
NSError* error = nil) {
session_task.state = NSURLSessionTaskStateCompleted;
[session_delegate() URLSession:session()
task:session_task
didCompleteWithError:error];
}
testing::StrictMock<FakeDownloadTaskImplDelegate> task_delegate_;
std::unique_ptr<DownloadTaskImpl> task_;
TestWebState web_state_;
MockDownloadTaskObserver task_observer_;
};
// Tests DownloadTaskImpl default state after construction.
TEST_F(DownloadTaskImplTest, DefaultState) {
EXPECT_FALSE(task_->GetResponseWriter());
EXPECT_NSEQ(task_delegate_.configuration().identifier,
task_->GetIndentifier());
EXPECT_EQ(kUrl, task_->GetOriginalUrl());
EXPECT_FALSE(task_->IsDone());
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(-1, task_->GetTotalBytes());
EXPECT_EQ(-1, task_->GetPercentComplete());
EXPECT_EQ(kContentDisposition, task_->GetContentDisposition());
EXPECT_EQ(kMimeType, task_->GetMimeType());
EXPECT_EQ("file.test", base::UTF16ToUTF8(task_->GetSuggestedFilename()));
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests sucessfull download of response without content.
// (No URLSession:dataTask:didReceiveData: callback).
TEST_F(DownloadTaskImplTest, EmptyContentDownload) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
// Download has finished.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
SimulateDownloadCompletion(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(0, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests sucessfull download of response with only one
// URLSession:dataTask:didReceiveData: callback.
TEST_F(DownloadTaskImplTest, SmallResponseDownload) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
// The response has arrived.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
const char kData[] = "foo";
int64_t kDataSize = strlen(kData);
session_task.countOfBytesExpectedToReceive = kDataSize;
SimulateDataDownload(session_task, kData);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
EXPECT_FALSE(task_->IsDone());
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kDataSize, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_EQ(kData, task_->GetResponseWriter()->AsStringWriter()->data());
// Download has finished.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
SimulateDownloadCompletion(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kDataSize, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_EQ(kData, task_->GetResponseWriter()->AsStringWriter()->data());
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests sucessfull download of response with multiple
// URLSession:dataTask:didReceiveData: callbacks.
TEST_F(DownloadTaskImplTest, LargeResponseDownload) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
// The first part of the response has arrived.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
const char kData1[] = "foo";
const char kData2[] = "buzz";
int64_t kData1Size = strlen(kData1);
int64_t kData2Size = strlen(kData2);
session_task.countOfBytesExpectedToReceive = kData1Size + kData2Size;
SimulateDataDownload(session_task, kData1);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
EXPECT_FALSE(task_->IsDone());
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
EXPECT_EQ(42, task_->GetPercentComplete());
net::URLFetcherStringWriter* writer =
task_->GetResponseWriter()->AsStringWriter();
EXPECT_EQ(kData1, writer->data());
// The second part of the response has arrived.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
SimulateDataDownload(session_task, kData2);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
EXPECT_FALSE(task_->IsDone());
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_EQ(std::string(kData1) + kData2, writer->data());
// Download has finished.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
SimulateDownloadCompletion(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kData1Size + kData2Size, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_EQ(std::string(kData1) + kData2, writer->data());
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests failed download when URLSession:dataTask:didReceiveData: callback was
// not even called.
TEST_F(DownloadTaskImplTest, FailureInTheBeginning) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
// Download has failed.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
NSError* error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorNotConnectedToInternet
userInfo:nil];
SimulateDownloadCompletion(session_task, error);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
EXPECT_TRUE(task_->GetErrorCode() == net::ERR_INTERNET_DISCONNECTED);
EXPECT_EQ(0, task_->GetTotalBytes());
EXPECT_EQ(100, task_->GetPercentComplete());
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests failed download when URLSession:dataTask:didReceiveData: callback was
// called once.
TEST_F(DownloadTaskImplTest, FailureInTheMiddle) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
// A part of the response has arrived.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
const char kReceivedData[] = "foo";
int64_t kReceivedDataSize = strlen(kReceivedData);
int64_t kExpectedDataSize = kReceivedDataSize + 10;
session_task.countOfBytesExpectedToReceive = kExpectedDataSize;
SimulateDataDownload(session_task, kReceivedData);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
EXPECT_FALSE(task_->IsDone());
EXPECT_EQ(0, task_->GetErrorCode());
EXPECT_EQ(kExpectedDataSize, task_->GetTotalBytes());
EXPECT_EQ(23, task_->GetPercentComplete());
net::URLFetcherStringWriter* writer =
task_->GetResponseWriter()->AsStringWriter();
EXPECT_EQ(kReceivedData, writer->data());
// Download has failed.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
NSError* error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorNotConnectedToInternet
userInfo:nil];
SimulateDownloadCompletion(session_task, error);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
EXPECT_TRUE(task_->GetErrorCode() == net::ERR_INTERNET_DISCONNECTED);
EXPECT_EQ(kExpectedDataSize, task_->GetTotalBytes());
EXPECT_EQ(23, task_->GetPercentComplete());
EXPECT_EQ(kReceivedData, writer->data());
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests that NSURLSessionConfiguration contains up to date cookie from browser
// state before the download started.
TEST_F(DownloadTaskImplTest, Cookie) {
if (@available(iOS 11, *)) {
// Remove all cookies from the session configuration.
auto storage = task_delegate_.configuration().HTTPCookieStorage;
for (NSHTTPCookie* cookie in storage.cookies)
[storage deleteCookie:cookie];
// Add a cookie to BrowserState.
NSURL* cookie_url = [NSURL URLWithString:@(kUrl)];
NSHTTPCookie* cookie = [NSHTTPCookie cookieWithProperties:@{
NSHTTPCookieName : @"name",
NSHTTPCookieValue : @"value",
NSHTTPCookiePath : cookie_url.path,
NSHTTPCookieDomain : cookie_url.host,
NSHTTPCookieVersion : @1,
}];
ASSERT_TRUE(SetCookie(cookie));
// Start the download and make sure that all cookie from BrowserState were
// picked up.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
ASSERT_TRUE(Start());
EXPECT_EQ(1U, storage.cookies.count);
EXPECT_NSEQ(cookie, storage.cookies.firstObject);
}
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests that URLFetcherFileWriter deletes the file if download has failed with
// error.
TEST_F(DownloadTaskImplTest, FileDeletion) {
// Create URLFetcherFileWriter.
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath temp_file = temp_dir.GetPath().AppendASCII("DownloadTaskImpl");
base::DeleteFile(temp_file, false);
ASSERT_FALSE(base::PathExists(temp_file));
std::unique_ptr<net::URLFetcherResponseWriter> writer =
std::make_unique<net::URLFetcherFileWriter>(
base::ThreadTaskRunnerHandle::Get(), temp_file);
__block bool initialized_file_writer = false;
ASSERT_EQ(net::ERR_IO_PENDING, writer->Initialize(BindBlockArc(^(int error) {
ASSERT_FALSE(error);
initialized_file_writer = true;
})));
ASSERT_TRUE(WaitUntilConditionOrTimeout(1.0, ^{
base::RunLoop().RunUntilIdle();
return initialized_file_writer;
}));
// Start the download.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start(std::move(writer));
ASSERT_TRUE(session_task);
// Deliver the response and verify that download file exists.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
const char kReceivedData[] = "foo";
SimulateDataDownload(session_task, kReceivedData);
ASSERT_TRUE(base::PathExists(temp_file));
// Fail the download and verify that the file was deleted.
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
NSError* error = [NSError errorWithDomain:NSURLErrorDomain
code:NSURLErrorNotConnectedToInternet
userInfo:nil];
SimulateDownloadCompletion(session_task, error);
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForDownloadTimeout, ^{
return task_->IsDone();
}));
ASSERT_TRUE(WaitUntilConditionOrTimeout(kWaitForFileOperationTimeout, ^{
base::RunLoop().RunUntilIdle();
return !base::PathExists(temp_file);
}));
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
}
// Tests that destructing DownloadTaskImpl calls -[NSURLSessionDataTask cancel]
// and OnTaskDestroyed().
TEST_F(DownloadTaskImplTest, DownloadTaskDestruction) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
EXPECT_CALL(task_delegate_, OnTaskDestroyed(task_.get()));
task_->RemoveObserver(&task_observer_);
task_ = nullptr; // Destruct DownloadTaskImpl.
EXPECT_TRUE(session_task.state = NSURLSessionTaskStateCanceling);
}
// Tests that shutting down DownloadTaskImpl calls
// -[NSURLSessionDataTask cancel], but does not call OnTaskDestroyed().
TEST_F(DownloadTaskImplTest, DownloadTaskShutdown) {
EXPECT_CALL(task_observer_, OnDownloadUpdated(task_.get()));
CRWFakeNSURLSessionTask* session_task = Start();
ASSERT_TRUE(session_task);
testing::Mock::VerifyAndClearExpectations(&task_observer_);
task_->ShutDown();
EXPECT_TRUE(session_task.state = NSURLSessionTaskStateCanceling);
}
} // namespace web
...@@ -17,8 +17,6 @@ source_set("fakes") { ...@@ -17,8 +17,6 @@ source_set("fakes") {
sources = [ sources = [
"crw_fake_back_forward_list.h", "crw_fake_back_forward_list.h",
"crw_fake_back_forward_list.mm", "crw_fake_back_forward_list.mm",
"crw_fake_nsurl_session_task.h",
"crw_fake_nsurl_session_task.mm",
"fake_navigation_manager_delegate.h", "fake_navigation_manager_delegate.h",
"fake_navigation_manager_delegate.mm", "fake_navigation_manager_delegate.mm",
] ]
......
// Copyright 2017 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 IOS_WEB_TEST_FAKES_CRW_FAKE_NSURL_SESSION_TASK_H_
#define IOS_WEB_TEST_FAKES_CRW_FAKE_NSURL_SESSION_TASK_H_
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// Fake NSURLSessionDataTask class which can be used for testing. |cancel| and
// |resume| methods only change the |state| of this task without actually
// starting or stopping the download.
@interface CRWFakeNSURLSessionTask : NSURLSessionDataTask
// Redefined NSURLSessionTask properties as readwrite.
@property(nonatomic) int64_t countOfBytesReceived;
@property(nonatomic) int64_t countOfBytesExpectedToReceive;
@property(nonatomic) NSURLSessionTaskState state;
- (nullable instancetype)initWithURL:(NSURL*)URL NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
#endif // IOS_WEB_TEST_FAKES_CRW_FAKE_NSURL_SESSION_TASK_H_
// Copyright 2017 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 "ios/web/test/fakes/crw_fake_nsurl_session_task.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
@interface CRWFakeNSURLSessionTask ()
// NSURLSessionTask properties.
@property(nullable, readonly, copy) NSURLRequest* originalRequest;
@property(nullable, readonly, copy) NSURLRequest* currentRequest;
@end
@implementation CRWFakeNSURLSessionTask
@synthesize countOfBytesReceived = _countOfBytesReceived;
@synthesize countOfBytesExpectedToReceive = _countOfBytesExpectedToReceive;
@synthesize state = _state;
@synthesize originalRequest = _originalRequest;
@synthesize currentRequest = _currentRequest;
- (instancetype)initWithURL:(NSURL*)URL {
if ((self = [super init])) {
_state = NSURLSessionTaskStateSuspended;
_currentRequest = [NSURLRequest requestWithURL:URL];
_originalRequest = [NSURLRequest requestWithURL:URL];
}
return self;
}
- (void)cancel {
self.state = NSURLSessionTaskStateCanceling;
}
- (void)resume {
self.state = NSURLSessionTaskStateRunning;
}
// A private method, called by -[NSHTTPCookieStorage storeCookies:forTask:].
// Requires stubbing in order to use NSHTTPCookieStorage API.
- (NSString*)_storagePartitionIdentifier {
return nil;
}
@end
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