| Index: content/browser/loader/resource_dispatcher_host_unittest.cc
|
| diff --git a/content/browser/loader/resource_dispatcher_host_unittest.cc b/content/browser/loader/resource_dispatcher_host_unittest.cc
|
| index 6f7a8e238daccffe358f57835dfe9cb8fca83609..02993df8796cea85b27b38580f492cf79f31af79 100644
|
| --- a/content/browser/loader/resource_dispatcher_host_unittest.cc
|
| +++ b/content/browser/loader/resource_dispatcher_host_unittest.cc
|
| @@ -4,7 +4,9 @@
|
|
|
| #include <vector>
|
|
|
| +#include "base/basictypes.h"
|
| #include "base/bind.h"
|
| +#include "base/file_util.h"
|
| #include "base/files/file_path.h"
|
| #include "base/memory/scoped_vector.h"
|
| #include "base/message_loop/message_loop.h"
|
| @@ -32,6 +34,7 @@
|
| #include "content/public/common/process_type.h"
|
| #include "content/public/common/resource_response.h"
|
| #include "content/public/test/test_browser_context.h"
|
| +#include "content/public/test/test_browser_thread_bundle.h"
|
| #include "content/test/test_content_browser_client.h"
|
| #include "net/base/net_errors.h"
|
| #include "net/base/request_priority.h"
|
| @@ -46,10 +49,13 @@
|
| #include "net/url_request/url_request_test_util.h"
|
| #include "testing/gtest/include/gtest/gtest.h"
|
| #include "webkit/common/appcache/appcache_interfaces.h"
|
| +#include "webkit/common/blob/shareable_file_reference.h"
|
|
|
| // TODO(eroman): Write unit tests for SafeBrowsing that exercise
|
| // SafeBrowsingResourceHandler.
|
|
|
| +using webkit_blob::ShareableFileReference;
|
| +
|
| namespace content {
|
|
|
| namespace {
|
| @@ -86,6 +92,7 @@ static int RequestIDForMessage(const IPC::Message& msg) {
|
| case ResourceMsg_ReceivedRedirect::ID:
|
| case ResourceMsg_SetDataBuffer::ID:
|
| case ResourceMsg_DataReceived::ID:
|
| + case ResourceMsg_DataDownloaded::ID:
|
| case ResourceMsg_RequestComplete::ID: {
|
| bool result = PickleIterator(msg).ReadInt(&request_id);
|
| DCHECK(result);
|
| @@ -181,6 +188,7 @@ class ForwardingFilter : public ResourceMessageFilter {
|
| base::Unretained(this))),
|
| dest_(dest),
|
| resource_context_(resource_context) {
|
| + ChildProcessSecurityPolicyImpl::GetInstance()->Add(child_id());
|
| set_peer_pid_for_testing(base::GetCurrentProcId());
|
| }
|
|
|
| @@ -559,32 +567,47 @@ class TestResourceDispatcherHostDelegate
|
| scoped_ptr<base::SupportsUserData::Data> user_data_;
|
| };
|
|
|
| +// Waits for a ShareableFileReference to be released.
|
| +class ShareableFileReleaseWaiter {
|
| + public:
|
| + ShareableFileReleaseWaiter(const base::FilePath& path) {
|
| + scoped_refptr<ShareableFileReference> file =
|
| + ShareableFileReference::Get(path);
|
| + file->AddFinalReleaseCallback(
|
| + base::Bind(&ShareableFileReleaseWaiter::Released,
|
| + base::Unretained(this)));
|
| + }
|
| +
|
| + void Wait() {
|
| + loop_.Run();
|
| + }
|
| +
|
| + private:
|
| + void Released(const base::FilePath& path) {
|
| + loop_.Quit();
|
| + }
|
| +
|
| + base::RunLoop loop_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(ShareableFileReleaseWaiter);
|
| +};
|
| +
|
| class ResourceDispatcherHostTest : public testing::Test,
|
| public IPC::Sender {
|
| public:
|
| ResourceDispatcherHostTest()
|
| - : ui_thread_(BrowserThread::UI, &message_loop_),
|
| - file_thread_(BrowserThread::FILE_USER_BLOCKING, &message_loop_),
|
| - cache_thread_(BrowserThread::CACHE, &message_loop_),
|
| - io_thread_(BrowserThread::IO, &message_loop_),
|
| + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP),
|
| old_factory_(NULL),
|
| send_data_received_acks_(false) {
|
| browser_context_.reset(new TestBrowserContext());
|
| BrowserContext::EnsureResourceContextInitialized(browser_context_.get());
|
| - message_loop_.RunUntilIdle();
|
| + base::RunLoop().RunUntilIdle();
|
| ResourceContext* resource_context = browser_context_->GetResourceContext();
|
| filter_ = new ForwardingFilter(this, resource_context);
|
| resource_context->GetRequestContext()->set_network_delegate(
|
| &network_delegate_);
|
| }
|
|
|
| - virtual ~ResourceDispatcherHostTest() {
|
| - for (std::set<int>::iterator it = child_ids_.begin();
|
| - it != child_ids_.end(); ++it) {
|
| - host_.CancelRequestsForProcess(*it);
|
| - }
|
| - }
|
| -
|
| // IPC::Sender implementation
|
| virtual bool Send(IPC::Message* msg) OVERRIDE {
|
| accum_.AddMessage(*msg);
|
| @@ -594,13 +617,18 @@ class ResourceDispatcherHostTest : public testing::Test,
|
| GenerateDataReceivedACK(*msg);
|
| }
|
|
|
| + if (wait_for_request_complete_loop_ &&
|
| + msg->type() == ResourceMsg_RequestComplete::ID) {
|
| + wait_for_request_complete_loop_->Quit();
|
| + }
|
| +
|
| delete msg;
|
| return true;
|
| }
|
|
|
| protected:
|
| // testing::Test
|
| - virtual void SetUp() {
|
| + virtual void SetUp() OVERRIDE {
|
| DCHECK(!test_fixture_);
|
| test_fixture_ = this;
|
| ChildProcessSecurityPolicyImpl::GetInstance()->Add(0);
|
| @@ -626,6 +654,11 @@ class ResourceDispatcherHostTest : public testing::Test,
|
| DCHECK(test_fixture_ == this);
|
| test_fixture_ = NULL;
|
|
|
| + for (std::set<int>::iterator it = child_ids_.begin();
|
| + it != child_ids_.end(); ++it) {
|
| + host_.CancelRequestsForProcess(*it);
|
| + }
|
| +
|
| host_.Shutdown();
|
|
|
| ChildProcessSecurityPolicyImpl::GetInstance()->Remove(0);
|
| @@ -638,7 +671,7 @@ class ResourceDispatcherHostTest : public testing::Test,
|
| WorkerServiceImpl::GetInstance()->PerformTeardownForTesting();
|
|
|
| browser_context_.reset();
|
| - message_loop_.RunUntilIdle();
|
| + base::RunLoop().RunUntilIdle();
|
| }
|
|
|
| // Creates a request using the current test object as the filter and
|
| @@ -774,11 +807,14 @@ class ResourceDispatcherHostTest : public testing::Test,
|
| return old_filter;
|
| }
|
|
|
| - base::MessageLoopForIO message_loop_;
|
| - BrowserThreadImpl ui_thread_;
|
| - BrowserThreadImpl file_thread_;
|
| - BrowserThreadImpl cache_thread_;
|
| - BrowserThreadImpl io_thread_;
|
| + void WaitForRequestComplete() {
|
| + DCHECK(!wait_for_request_complete_loop_);
|
| + wait_for_request_complete_loop_.reset(new base::RunLoop);
|
| + wait_for_request_complete_loop_->Run();
|
| + wait_for_request_complete_loop_.reset();
|
| + }
|
| +
|
| + content::TestBrowserThreadBundle thread_bundle_;
|
| scoped_ptr<TestBrowserContext> browser_context_;
|
| scoped_refptr<ForwardingFilter> filter_;
|
| net::TestNetworkDelegate network_delegate_;
|
| @@ -790,6 +826,7 @@ class ResourceDispatcherHostTest : public testing::Test,
|
| net::URLRequest::ProtocolFactory* old_factory_;
|
| bool send_data_received_acks_;
|
| std::set<int> child_ids_;
|
| + scoped_ptr<base::RunLoop> wait_for_request_complete_loop_;
|
| static ResourceDispatcherHostTest* test_fixture_;
|
| static bool delay_start_;
|
| static bool delay_complete_;
|
| @@ -2712,4 +2749,161 @@ TEST_F(ResourceDispatcherHostTest, DataReceivedUnexpectedACKs) {
|
| }
|
| }
|
|
|
| +// Tests the dispatcher host's temporary file management.
|
| +TEST_F(ResourceDispatcherHostTest, RegisterDownloadedTempFile) {
|
| + const int kRequestID = 1;
|
| +
|
| + // Create a temporary file.
|
| + base::FilePath file_path;
|
| + ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
|
| + scoped_refptr<ShareableFileReference> deletable_file =
|
| + ShareableFileReference::GetOrCreate(
|
| + file_path,
|
| + ShareableFileReference::DELETE_ON_FINAL_RELEASE,
|
| + BrowserThread::GetMessageLoopProxyForThread(
|
| + BrowserThread::FILE).get());
|
| +
|
| + // Not readable.
|
| + EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| +
|
| + // Register it for a resource request.
|
| + host_.RegisterDownloadedTempFile(filter_->child_id(), kRequestID, file_path);
|
| +
|
| + // Should be readable now.
|
| + EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| +
|
| + // The child releases from the request.
|
| + bool msg_was_ok = true;
|
| + ResourceHostMsg_ReleaseDownloadedFile release_msg(kRequestID);
|
| + host_.OnMessageReceived(release_msg, filter_, &msg_was_ok);
|
| + ASSERT_TRUE(msg_was_ok);
|
| +
|
| + // Still readable because there is another reference to the file. (The child
|
| + // may take additional blob references.)
|
| + EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| +
|
| + // Release extra references and wait for the file to be deleted. (This relies
|
| + // on the delete happening on the FILE thread which is mapped to main thread
|
| + // in this test.)
|
| + deletable_file = NULL;
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + // The file is no longer readable to the child and has been deleted.
|
| + EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| + EXPECT_FALSE(base::PathExists(file_path));
|
| +}
|
| +
|
| +// Tests that temporary files held on behalf of child processes are released
|
| +// when the child process dies.
|
| +TEST_F(ResourceDispatcherHostTest, ReleaseTemporiesOnProcessExit) {
|
| + const int kRequestID = 1;
|
| +
|
| + // Create a temporary file.
|
| + base::FilePath file_path;
|
| + ASSERT_TRUE(base::CreateTemporaryFile(&file_path));
|
| + scoped_refptr<ShareableFileReference> deletable_file =
|
| + ShareableFileReference::GetOrCreate(
|
| + file_path,
|
| + ShareableFileReference::DELETE_ON_FINAL_RELEASE,
|
| + BrowserThread::GetMessageLoopProxyForThread(
|
| + BrowserThread::FILE).get());
|
| +
|
| + // Register it for a resource request.
|
| + host_.RegisterDownloadedTempFile(filter_->child_id(), kRequestID, file_path);
|
| + deletable_file = NULL;
|
| +
|
| + // Should be readable now.
|
| + EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| +
|
| + // Let the process die.
|
| + filter_->OnChannelClosing();
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + // The file is no longer readable to the child and has been deleted.
|
| + EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), file_path));
|
| + EXPECT_FALSE(base::PathExists(file_path));
|
| +}
|
| +
|
| +TEST_F(ResourceDispatcherHostTest, DownloadToFile) {
|
| + // Make a request which downloads to file.
|
| + ResourceHostMsg_Request request = CreateResourceRequest(
|
| + "GET", ResourceType::SUB_RESOURCE, net::URLRequestTestJob::test_url_1());
|
| + request.download_to_file = true;
|
| + ResourceHostMsg_RequestResource request_msg(0, 1, request);
|
| + bool msg_was_ok;
|
| + host_.OnMessageReceived(request_msg, filter_, &msg_was_ok);
|
| + ASSERT_TRUE(msg_was_ok);
|
| +
|
| + // Running the message loop until idle does not work because
|
| + // RedirectToFileResourceHandler posts things to base::WorkerPool. Instead,
|
| + // wait for the ResourceMsg_RequestComplete to go out. Then run the event loop
|
| + // until idle so the loader is gone.
|
| + WaitForRequestComplete();
|
| + base::RunLoop().RunUntilIdle();
|
| + EXPECT_EQ(0, host_.pending_requests());
|
| +
|
| + ResourceIPCAccumulator::ClassifiedMessages msgs;
|
| + accum_.GetClassifiedMessages(&msgs);
|
| +
|
| + ASSERT_EQ(1U, msgs.size());
|
| + const std::vector<IPC::Message>& messages = msgs[0];
|
| +
|
| + // The request should contain the following messages:
|
| + // ReceivedResponse (indicates headers received and filename)
|
| + // DataDownloaded* (bytes downloaded and total length)
|
| + // RequestComplete (request is done)
|
| +
|
| + // ReceivedResponse
|
| + ResourceResponseHead response_head;
|
| + GetResponseHead(messages, &response_head);
|
| + ASSERT_FALSE(response_head.download_file_path.empty());
|
| +
|
| + // DataDownloaded
|
| + size_t total_len = 0;
|
| + for (size_t i = 1; i < messages.size() - 1; i++) {
|
| + ASSERT_EQ(ResourceMsg_DataDownloaded::ID, messages[i].type());
|
| + PickleIterator iter(messages[i]);
|
| + int request_id, data_len;
|
| + ASSERT_TRUE(IPC::ReadParam(&messages[i], &iter, &request_id));
|
| + ASSERT_TRUE(IPC::ReadParam(&messages[i], &iter, &data_len));
|
| + total_len += data_len;
|
| + }
|
| + EXPECT_EQ(net::URLRequestTestJob::test_data_1().size(), total_len);
|
| +
|
| + // RequestComplete
|
| + CheckRequestCompleteErrorCode(messages.back(), net::OK);
|
| +
|
| + // Verify that the data ended up in the temporary file.
|
| + std::string contents;
|
| + ASSERT_TRUE(base::ReadFileToString(response_head.download_file_path,
|
| + &contents));
|
| + EXPECT_EQ(net::URLRequestTestJob::test_data_1(), contents);
|
| +
|
| + // The file should be readable by the child.
|
| + EXPECT_TRUE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), response_head.download_file_path));
|
| +
|
| + // When the renderer releases the file, it should be deleted. Again,
|
| + // RunUntilIdle doesn't work because base::WorkerPool is involved.
|
| + ShareableFileReleaseWaiter waiter(response_head.download_file_path);
|
| + ResourceHostMsg_ReleaseDownloadedFile release_msg(1);
|
| + host_.OnMessageReceived(release_msg, filter_, &msg_was_ok);
|
| + ASSERT_TRUE(msg_was_ok);
|
| + waiter.Wait();
|
| + // The release callback runs before the delete is scheduled, so pump the
|
| + // message loop for the delete itself. (This relies on the delete happening on
|
| + // the FILE thread which is mapped to main thread in this test.)
|
| + base::RunLoop().RunUntilIdle();
|
| +
|
| + EXPECT_FALSE(base::PathExists(response_head.download_file_path));
|
| + EXPECT_FALSE(ChildProcessSecurityPolicyImpl::GetInstance()->CanReadFile(
|
| + filter_->child_id(), response_head.download_file_path));
|
| +}
|
| +
|
| } // namespace content
|
|
|