From 27fac646d9f16d7963f01c36f3b5f65f5d62321b Mon Sep 17 00:00:00 2001 From: Marshall Greenblatt Date: Mon, 15 Jul 2013 20:25:59 +0000 Subject: [PATCH] Fix mapping of routing IDs to browser objects (issue #1012). git-svn-id: https://chromiumembedded.googlecode.com/svn/trunk@1305 5089003a-bbd8-11dd-ad1f-f1f9622dbc98 --- cef.gyp | 2 + libcef/browser/browser_host_impl.cc | 24 +- libcef/browser/browser_host_impl.h | 4 +- libcef/browser/browser_info.cc | 46 ++- libcef/browser/browser_info.h | 21 +- libcef/browser/content_browser_client.cc | 2 +- tests/unittests/client_app_delegates.cc | 4 + tests/unittests/request_handler_unittest.cc | 396 ++++++++++++++++++++ tests/unittests/test_handler.cc | 83 +++- tests/unittests/test_handler.h | 78 +++- 10 files changed, 623 insertions(+), 37 deletions(-) create mode 100644 tests/unittests/request_handler_unittest.cc diff --git a/cef.gyp b/cef.gyp index b8157274b..3cd7cb82d 100644 --- a/cef.gyp +++ b/cef.gyp @@ -256,6 +256,7 @@ 'tests/unittests/navigation_unittest.cc', 'tests/unittests/os_rendering_unittest.cc', 'tests/unittests/process_message_unittest.cc', + 'tests/unittests/request_handler_unittest.cc', 'tests/unittests/request_unittest.cc', 'tests/cefclient/resource_util.h', 'tests/unittests/run_all_unittests.cc', @@ -1277,6 +1278,7 @@ 'tests/unittests/dom_unittest.cc', 'tests/unittests/navigation_unittest.cc', 'tests/unittests/process_message_unittest.cc', + 'tests/unittests/request_handler_unittest.cc', 'tests/unittests/scheme_handler_unittest.cc', 'tests/unittests/urlrequest_unittest.cc', 'tests/unittests/test_handler.cc', diff --git a/libcef/browser/browser_host_impl.cc b/libcef/browser/browser_host_impl.cc index 9c3244b04..2a3679556 100644 --- a/libcef/browser/browser_host_impl.cc +++ b/libcef/browser/browser_host_impl.cc @@ -388,11 +388,12 @@ CefRefPtr CefBrowserHostImpl::GetBrowserForRequest( DCHECK(request); CEF_REQUIRE_IOT(); int render_process_id = -1; - int render_view_id = -1; + int render_view_id = MSG_ROUTING_NONE; - if (!content::ResourceRequestInfo::GetRenderViewForRequest(request, - &render_process_id, - &render_view_id)) { + if (!content::ResourceRequestInfo::GetRenderViewForRequest( + request, &render_process_id, &render_view_id) || + render_process_id == -1 || + render_view_id == MSG_ROUTING_NONE) { return NULL; } @@ -402,6 +403,9 @@ CefRefPtr CefBrowserHostImpl::GetBrowserForRequest( // static CefRefPtr CefBrowserHostImpl::GetBrowserByRoutingID( int render_process_id, int render_view_id) { + if (render_process_id == -1 || render_view_id == MSG_ROUTING_NONE) + return NULL; + if (CEF_CURRENTLY_ON_UIT()) { // Use the non-thread-safe but potentially faster approach. content::RenderViewHost* render_view_host = @@ -445,7 +449,7 @@ CefRefPtr CefBrowserHostImpl::GetBrowserByChildID( return NULL; } else { // Use the thread-safe approach. - return _Context->GetBrowserByRoutingID(render_process_id, 0); + return _Context->GetBrowserByRoutingID(render_process_id, -1); } } @@ -1793,11 +1797,8 @@ void CefBrowserHostImpl::RequestMediaAccessPermission( void CefBrowserHostImpl::RenderViewCreated( content::RenderViewHost* render_view_host) { - // When navigating cross-origin the new (pending) RenderViewHost will be - // created before the old (current) RenderViewHost is destroyed. It may be - // necessary in the future to track both current and pending render IDs. - browser_info_->set_render_ids(render_view_host->GetProcess()->GetID(), - render_view_host->GetRoutingID()); + browser_info_->add_render_id(render_view_host->GetProcess()->GetID(), + render_view_host->GetRoutingID()); // Update the DevTools URLs, if any. CefDevToolsDelegate* devtools_delegate = _Context->devtools_delegate(); @@ -1820,6 +1821,9 @@ void CefBrowserHostImpl::RenderViewCreated( void CefBrowserHostImpl::RenderViewDeleted( content::RenderViewHost* render_view_host) { + browser_info_->remove_render_id(render_view_host->GetProcess()->GetID(), + render_view_host->GetRoutingID()); + if (registrar_->IsRegistered( this, content::NOTIFICATION_FOCUS_CHANGED_IN_PAGE, content::Source(render_view_host))) { diff --git a/libcef/browser/browser_host_impl.h b/libcef/browser/browser_host_impl.h index ef714e03d..4987be12c 100644 --- a/libcef/browser/browser_host_impl.h +++ b/libcef/browser/browser_host_impl.h @@ -100,7 +100,9 @@ class CefBrowserHostImpl : public CefBrowserHost, // Returns the browser associated with the specified routing IDs. static CefRefPtr GetBrowserByRoutingID( int render_process_id, int render_view_id); - // Returns the browser associated with the specified child process ID. + // Returns the first browser associated with the specified child process ID. + // There may be multiple browsers using the same render process so this method + // should be used with caution. static CefRefPtr GetBrowserByChildID( int render_process_id); diff --git a/libcef/browser/browser_info.cc b/libcef/browser/browser_info.cc index 29d06d24f..85f85f508 100644 --- a/libcef/browser/browser_info.cc +++ b/libcef/browser/browser_info.cc @@ -8,9 +8,7 @@ CefBrowserInfo::CefBrowserInfo(int browser_id, bool is_popup) : browser_id_(browser_id), is_popup_(is_popup), - is_window_rendering_disabled_(false), - render_process_id_(MSG_ROUTING_NONE), - render_view_id_(MSG_ROUTING_NONE) { + is_window_rendering_disabled_(false) { DCHECK_GT(browser_id, 0); } @@ -21,19 +19,51 @@ void CefBrowserInfo::set_window_rendering_disabled(bool disabled) { is_window_rendering_disabled_ = disabled; } -void CefBrowserInfo::set_render_ids( +void CefBrowserInfo::add_render_id( int render_process_id, int render_view_id) { + DCHECK_GT(render_process_id, 0); + DCHECK_GT(render_view_id, 0); + base::AutoLock lock_scope(lock_); - render_process_id_ = render_process_id; - render_view_id_ = render_view_id; + + if (!render_id_set_.empty()) { + RenderIdSet::const_iterator it = + render_id_set_.find(std::make_pair(render_process_id, render_view_id)); + if (it != render_id_set_.end()) + return; + } + + render_id_set_.insert(std::make_pair(render_process_id, render_view_id)); +} + +void CefBrowserInfo::remove_render_id( + int render_process_id, int render_view_id) { + DCHECK_GT(render_process_id, 0); + DCHECK_GT(render_view_id, 0); + + base::AutoLock lock_scope(lock_); + + DCHECK(!render_id_set_.empty()); + if (render_id_set_.empty()) + return; + + RenderIdSet::iterator it = + render_id_set_.find(std::make_pair(render_process_id, render_view_id)); + DCHECK(it != render_id_set_.end()); + if (it != render_id_set_.end()) + render_id_set_.erase(it); } bool CefBrowserInfo::is_render_id_match( int render_process_id, int render_view_id) { base::AutoLock lock_scope(lock_); - if (render_process_id != render_process_id_) + + if (render_id_set_.empty()) return false; - return (render_view_id == 0 || render_view_id == render_view_id_); + + RenderIdSet::const_iterator it = + render_id_set_.find(std::make_pair(render_process_id, render_view_id)); + return (it != render_id_set_.end()); } CefRefPtr CefBrowserInfo::browser() { diff --git a/libcef/browser/browser_info.h b/libcef/browser/browser_info.h index 6c38abb78..710a086e9 100644 --- a/libcef/browser/browser_info.h +++ b/libcef/browser/browser_info.h @@ -6,6 +6,8 @@ #define CEF_LIBCEF_BROWSER_BROWSER_INFO_H_ #pragma once +#include + #include "libcef/browser/browser_host_impl.h" #include "base/memory/ref_counted.h" @@ -28,10 +30,11 @@ class CefBrowserInfo : public base::RefCountedThreadSafe { void set_window_rendering_disabled(bool disabled); - void set_render_ids(int render_process_id, int render_view_id); + void add_render_id(int render_process_id, int render_view_id); + void remove_render_id(int render_process_id, int render_view_id); // Returns true if this browser matches the specified ID values. If - // |render_view_id| is 0 any browser with the specified |render_process_id| + // |render_view_id| is -1 any browser with the specified |render_process_id| // will match. bool is_render_id_match(int render_process_id, int render_view_id); @@ -46,8 +49,18 @@ class CefBrowserInfo : public base::RefCountedThreadSafe { base::Lock lock_; // The below members must be protected by |lock_|. - int render_process_id_; - int render_view_id_; + + // Set of mapped (process_id, view_id) pairs. Keeping this set is necessary + // for the following reasons: + // 1. When navigating cross-origin the new (pending) RenderViewHost will be + // created before the old (current) RenderViewHost is destroyed. + // 2. When canceling and asynchronously continuing navigation of the same URL + // a new RenderViewHost may be created for the first (canceled) navigation + // and then destroyed as a result of the second (allowed) navigation. + // 3. Out-of-process iframes have their own render IDs which must also be + // associated with the host browser. + typedef std::set > RenderIdSet; + RenderIdSet render_id_set_; // May be NULL if the browser has not yet been created or if the browser has // been destroyed. diff --git a/libcef/browser/content_browser_client.cc b/libcef/browser/content_browser_client.cc index 37a0d4d9f..50e54a707 100644 --- a/libcef/browser/content_browser_client.cc +++ b/libcef/browser/content_browser_client.cc @@ -290,7 +290,7 @@ scoped_refptr // Must be a popup if it hasn't already been created. scoped_refptr browser_info = new CefBrowserInfo(++next_browser_id_, true); - browser_info->set_render_ids(render_process_id, render_view_id); + browser_info->add_render_id(render_process_id, render_view_id); browser_info_list_.push_back(browser_info); return browser_info; } diff --git a/tests/unittests/client_app_delegates.cc b/tests/unittests/client_app_delegates.cc index bf2a63268..65a05f064 100644 --- a/tests/unittests/client_app_delegates.cc +++ b/tests/unittests/client_app_delegates.cc @@ -37,6 +37,10 @@ void ClientApp::CreateRenderDelegates(RenderDelegateSet& delegates) { // Bring in the Navigation tests. extern void CreateNavigationRendererTests(RenderDelegateSet& delegates); CreateNavigationRendererTests(delegates); + + // Bring in the RequestHandler tests. + extern void CreateRequestHandlerRendererTests(RenderDelegateSet& delegates); + CreateRequestHandlerRendererTests(delegates); } // static diff --git a/tests/unittests/request_handler_unittest.cc b/tests/unittests/request_handler_unittest.cc new file mode 100644 index 000000000..e0f9b237c --- /dev/null +++ b/tests/unittests/request_handler_unittest.cc @@ -0,0 +1,396 @@ +// Copyright (c) 2013 The Chromium Embedded Framework 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 "tests/unittests/test_handler.h" +#include "base/strings/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "include/cef_cookie.h" +#include "include/cef_runnable.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "tests/cefclient/client_app.h" + +namespace { + +enum NetNotifyTestType { + NNTT_NONE = 0, + NNTT_NORMAL, + NNTT_DELAYED_RENDERER, + NNTT_DELAYED_BROWSER, +}; + +const char kNetNotifyOrigin1[] = "http://tests-netnotify1/"; +const char kNetNotifyOrigin2[] = "http://tests-netnotify2/"; +const char kNetNotifyMsg[] = "RequestHandlerTest.NetNotify"; + +// Browser side. +class NetNotifyTestHandler : public TestHandler { + public: + NetNotifyTestHandler(CompletionState* completion_state, + NetNotifyTestType test_type, + bool same_origin) + : TestHandler(completion_state), + test_type_(test_type), + same_origin_(same_origin) {} + + virtual void SetupTest() OVERRIDE { + url1_ = base::StringPrintf("%snav1.html?t=%d", + kNetNotifyOrigin1, test_type_); + url2_ = base::StringPrintf("%snav2.html?t=%d", + same_origin_ ? kNetNotifyOrigin1 : kNetNotifyOrigin2, test_type_); + + cookie_manager_ = CefCookieManager::CreateManager(CefString(), true); + + AddResource(url1_, + "" + "" + "Nav1" + "", "text/html"); + AddResource(url2_, + "" + "" + "Nav2" + "", "text/html"); + + // Create browser that loads the 1st URL. + CreateBrowser(url1_); + } + + virtual void RunTest() OVERRIDE { + // Navigate to the 2nd URL. + GetBrowser()->GetMainFrame()->LoadURL(url2_); + } + + virtual bool OnBeforeResourceLoad(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) OVERRIDE { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + const std::string& url = request->GetURL(); + if (url.find(url1_) == 0) + got_before_resource_load1_.yes(); + else if (url.find(url2_) == 0) + got_before_resource_load2_.yes(); + else + EXPECT_TRUE(false); // Not reached + + return false; + } + + virtual CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) OVERRIDE { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + const std::string& url = request->GetURL(); + if (url.find(url1_) == 0) + got_get_resource_handler1_.yes(); + else if (url.find(url2_) == 0) + got_get_resource_handler2_.yes(); + else + EXPECT_TRUE(false); // Not reached + + return TestHandler::GetResourceHandler(browser, frame, request); + } + + virtual CefRefPtr GetCookieManager( + CefRefPtr browser, + const CefString& main_url) OVERRIDE { + EXPECT_TRUE(CefCurrentlyOn(TID_IO)); + + const std::string& url = main_url; + if (url.find(url1_) == 0) + got_get_cookie_manager1_.yes(); + else if (url.find(url2_) == 0) + got_get_cookie_manager2_.yes(); + else + EXPECT_TRUE(false); // Not reached + + return cookie_manager_; + } + + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + const std::string& url = frame->GetURL(); + if (url.find(url1_) == 0) { + got_load_end1_.yes(); + SetupComplete(); + } else if (url.find(url2_) == 0) { + got_load_end2_.yes(); + FinishTest(); + } else { + EXPECT_TRUE(false); // Not reached + } + } + + virtual bool OnProcessMessageReceived( + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE { + if (message->GetName().ToString() == kNetNotifyMsg) { + CefRefPtr args = message->GetArgumentList(); + EXPECT_TRUE(args.get()); + + NetNotifyTestType test_type = + static_cast(args->GetInt(0)); + EXPECT_EQ(test_type, test_type_); + + std::string url = args->GetString(1); + if (url.find(url1_) == 0) + got_process_message1_.yes(); + else if (url.find(url2_) == 0) + got_process_message2_.yes(); + else + EXPECT_TRUE(false); // Not reached + + // Navigating cross-origin from the browser process will cause a new + // render process to be created. We therefore need some information in + // the request itself to tell us that the navigation has already been + // delayed. + url += "&delayed=true"; + + if (test_type == NNTT_DELAYED_RENDERER) { + // Load the URL from the render process. + CefRefPtr message = + CefProcessMessage::Create(kNetNotifyMsg); + CefRefPtr args = message->GetArgumentList(); + args->SetInt(0, test_type); + args->SetString(1, url); + EXPECT_TRUE(browser->SendProcessMessage(PID_RENDERER, message)); + } else { + // Load the URL from the browser process. + browser->GetMainFrame()->LoadURL(url); + } + return true; + } + + // Message not handled. + return false; + } + + protected: + void FinishTest() { + //Verify that cookies were set correctly. + class TestVisitor : public CefCookieVisitor { + public: + explicit TestVisitor(NetNotifyTestHandler* handler) + : handler_(handler) { + } + virtual ~TestVisitor() { + // Destroy the test. + CefPostTask(TID_UI, + NewCefRunnableMethod(handler_, &NetNotifyTestHandler::DestroyTest)); + } + + virtual bool Visit(const CefCookie& cookie, int count, int total, + bool& deleteCookie) { + const std::string& name = CefString(&cookie.name); + const std::string& value = CefString(&cookie.value); + if (name == "name1" && value == "value1") + handler_->got_cookie1_.yes(); + else if (name == "name2" && value == "value2") + handler_->got_cookie2_.yes(); + return true; + } + + private: + NetNotifyTestHandler* handler_; + IMPLEMENT_REFCOUNTING(TestVisitor); + }; + + cookie_manager_->VisitAllCookies(new TestVisitor(this)); + } + + virtual void DestroyTest() OVERRIDE { + int browser_id = GetBrowser()->GetIdentifier(); + + // Verify test expectations. + EXPECT_TRUE(got_load_end1_) << " browser " << browser_id; + EXPECT_TRUE(got_before_resource_load1_) << " browser " << browser_id; + EXPECT_TRUE(got_get_resource_handler1_) << " browser " << browser_id; + EXPECT_TRUE(got_get_cookie_manager1_) << " browser " << browser_id; + EXPECT_TRUE(got_cookie1_) << " browser " << browser_id; + EXPECT_TRUE(got_load_end2_) << " browser " << browser_id; + EXPECT_TRUE(got_before_resource_load2_) << " browser " << browser_id; + EXPECT_TRUE(got_get_resource_handler2_) << " browser " << browser_id; + EXPECT_TRUE(got_get_cookie_manager2_) << " browser " << browser_id; + EXPECT_TRUE(got_cookie2_) << " browser " << browser_id; + + if (test_type_ == NNTT_DELAYED_RENDERER || + test_type_ == NNTT_DELAYED_BROWSER) { + EXPECT_TRUE(got_process_message1_) << " browser " << browser_id; + EXPECT_TRUE(got_process_message2_) << " browser " << browser_id; + } else { + EXPECT_FALSE(got_process_message1_) << " browser " << browser_id; + EXPECT_FALSE(got_process_message2_) << " browser " << browser_id; + } + + cookie_manager_ = NULL; + + TestHandler::DestroyTest(); + } + + NetNotifyTestType test_type_; + bool same_origin_; + std::string url1_; + std::string url2_; + + CefRefPtr cookie_manager_; + + TrackCallback got_load_end1_; + TrackCallback got_before_resource_load1_; + TrackCallback got_get_resource_handler1_; + TrackCallback got_get_cookie_manager1_; + TrackCallback got_cookie1_; + TrackCallback got_process_message1_; + TrackCallback got_load_end2_; + TrackCallback got_before_resource_load2_; + TrackCallback got_get_resource_handler2_; + TrackCallback got_get_cookie_manager2_; + TrackCallback got_cookie2_; + TrackCallback got_process_message2_; +}; + +// Renderer side. +class NetNotifyRendererTest : public ClientApp::RenderDelegate { + public: + NetNotifyRendererTest() {} + + virtual bool OnBeforeNavigation(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + cef_navigation_type_t navigation_type, + bool is_redirect) OVERRIDE { + const std::string& url = request->GetURL(); + + // Don't execute this method for unrelated tests. + if (url.find(kNetNotifyOrigin1) == std::string::npos && + url.find(kNetNotifyOrigin2) == std::string::npos) { + return false; + } + + NetNotifyTestType test_type = NNTT_NONE; + + // Extract the test type. + int pos = url.find("t="); + int intval = 0; + if (pos > 0 && base::StringToInt(url.substr(pos + 2, 1), &intval)) + test_type = static_cast(intval); + EXPECT_GT(test_type, NNTT_NONE); + + // Check if the load has already been delayed. + bool delay_loaded = (url.find("delayed=true") != std::string::npos); + + if (!delay_loaded && (test_type == NNTT_DELAYED_RENDERER || + test_type == NNTT_DELAYED_BROWSER)) { + // Delay load the URL. + CefRefPtr message = + CefProcessMessage::Create(kNetNotifyMsg); + CefRefPtr args = message->GetArgumentList(); + args->SetInt(0, test_type); + args->SetString(1, url); + EXPECT_TRUE(browser->SendProcessMessage(PID_BROWSER, message)); + + return true; + } + + return false; + } + + virtual bool OnProcessMessageReceived( + CefRefPtr app, + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE { + if (message->GetName().ToString() == kNetNotifyMsg) { + CefRefPtr args = message->GetArgumentList(); + EXPECT_TRUE(args.get()); + + NetNotifyTestType test_type = + static_cast(args->GetInt(0)); + EXPECT_EQ(test_type, NNTT_DELAYED_RENDERER); + + const std::string& url = args->GetString(1); + + // Load the URL from the render process. + browser->GetMainFrame()->LoadURL(url); + return true; + } + + // Message not handled. + return false; + } + + IMPLEMENT_REFCOUNTING(NetNotifyRendererTest); +}; + +void RunNetNotifyTest(NetNotifyTestType test_type, bool same_origin) { + TestHandler::CompletionState completion_state(3); + + CefRefPtr handler1 = + new NetNotifyTestHandler(&completion_state, test_type, same_origin); + CefRefPtr handler2 = + new NetNotifyTestHandler(&completion_state, test_type, same_origin); + CefRefPtr handler3 = + new NetNotifyTestHandler(&completion_state, test_type, same_origin); + + TestHandler::Collection collection(&completion_state); + collection.AddTestHandler(handler1.get()); + collection.AddTestHandler(handler2.get()); + collection.AddTestHandler(handler3.get()); + + collection.ExecuteTests(); +} + +} // namespace + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from the same origin and is not delayed. +TEST(RequestHandlerTest, NotificationsSameOriginDirect) { + RunNetNotifyTest(NNTT_NORMAL, true); +} + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from the same origin and is continued asynchronously from the +// render process. +TEST(RequestHandlerTest, NotificationsSameOriginDelayedRenderer) { + RunNetNotifyTest(NNTT_DELAYED_RENDERER, true); +} + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from the same origin and is continued asynchronously from the +// browser process. +TEST(RequestHandlerTest, NotificationsSameOriginDelayedBrowser) { + RunNetNotifyTest(NNTT_DELAYED_BROWSER, true); +} + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from a different origin and is not delayed. +TEST(RequestHandlerTest, NotificationsCrossOriginDirect) { + RunNetNotifyTest(NNTT_NORMAL, false); +} + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from a different origin and is continued asynchronously from +// the render process. +TEST(RequestHandlerTest, NotificationsCrossOriginDelayedRenderer) { + RunNetNotifyTest(NNTT_DELAYED_RENDERER, false); +} + +// Verify network notifications for multiple browsers existing simultaniously. +// URL loading is from a different origin and is continued asynchronously from +// the browser process. +TEST(RequestHandlerTest, NotificationsCrossOriginDelayedBrowser) { + RunNetNotifyTest(NNTT_DELAYED_BROWSER, false); +} + + +// Entry point for creating request handler renderer test objects. +// Called from client_app_delegates.cc. +void CreateRequestHandlerRendererTests( + ClientApp::RenderDelegateSet& delegates) { + delegates.insert(new NetNotifyRendererTest); +} diff --git a/tests/unittests/test_handler.cc b/tests/unittests/test_handler.cc index 854fe822f..5e19418cc 100644 --- a/tests/unittests/test_handler.cc +++ b/tests/unittests/test_handler.cc @@ -3,6 +3,7 @@ // can be found in the LICENSE file. #include "tests/unittests/test_handler.h" +#include "base/logging.h" #include "include/cef_command_line.h" #include "include/cef_runnable.h" #include "include/cef_stream.h" @@ -17,16 +18,80 @@ void NotifyEvent(base::WaitableEvent* event) { } // namespace +// TestHandler::CompletionState + +TestHandler::CompletionState::CompletionState(int total) + : total_(total), + count_(0), + event_(true, false) { +} + +void TestHandler::CompletionState::TestComplete() { + if (++count_ == total_) { + // Signal that the test is now complete. + event_.Signal(); + count_ = 0; + } +} + +void TestHandler::CompletionState::WaitForTests() { + // Wait for the test to complete + event_.Wait(); + + // Reset the event so the same test can be executed again. + event_.Reset(); +} + + +// TestHandler::Collection + +TestHandler::Collection::Collection(CompletionState* completion_state) + : completion_state_(completion_state) { + DCHECK(completion_state_); +} + +void TestHandler::Collection::AddTestHandler(TestHandler* test_handler) { + DCHECK_EQ(test_handler->completion_state_, completion_state_); + handler_list_.push_back(test_handler); +} + +void TestHandler::Collection::ExecuteTests() { + DCHECK_GT(handler_list_.size(), 0UL); + + TestHandlerList::const_iterator it; + + it = handler_list_.begin(); + for (; it != handler_list_.end(); ++it) + (*it)->SetupTest(); + + completion_state_->WaitForTests(); + + it = handler_list_.begin(); + for (; it != handler_list_.end(); ++it) + (*it)->RunTest(); + + completion_state_->WaitForTests(); +} + + // TestHandler int TestHandler::browser_count_ = 0; -TestHandler::TestHandler() - : browser_id_(0), - completion_event_(true, false) { +TestHandler::TestHandler(CompletionState* completion_state) + : browser_id_(0) { + if (completion_state) { + completion_state_ = completion_state; + completion_state_owned_ = false; + } else { + completion_state_ = new CompletionState(1); + completion_state_owned_ = true; + } } TestHandler::~TestHandler() { + if (completion_state_owned_) + delete completion_state_; } void TestHandler::OnAfterCreated(CefRefPtr browser) { @@ -49,7 +114,7 @@ void TestHandler::OnBeforeClose(CefRefPtr browser) { browser_id_ = 0; // Signal that the test is now complete. - completion_event_.Signal(); + completion_state_->TestComplete(); } } @@ -86,14 +151,18 @@ CefRefPtr TestHandler::GetResourceHandler( } void TestHandler::ExecuteTest() { + DCHECK_EQ(completion_state_->total(), 1); + // Run the test RunTest(); // Wait for the test to complete - completion_event_.Wait(); + completion_state_->WaitForTests(); +} - // Reset the event so the same test can be executed again. - completion_event_.Reset(); +void TestHandler::SetupComplete() { + // Signal that the test setup is complete. + completion_state_->TestComplete(); } void TestHandler::DestroyTest() { diff --git a/tests/unittests/test_handler.h b/tests/unittests/test_handler.h index 8ead855cd..6a5f9757c 100644 --- a/tests/unittests/test_handler.h +++ b/tests/unittests/test_handler.h @@ -6,6 +6,7 @@ #define CEF_TESTS_UNITTESTS_TEST_HANDLER_H_ #pragma once +#include #include #include #include @@ -28,6 +29,7 @@ class TrackCallback { bool gotit_; }; + // Base implementation of CefClient for unit tests. Add new interfaces as needed // by test cases. class TestHandler : public CefClient, @@ -40,10 +42,68 @@ class TestHandler : public CefClient, public CefLoadHandler, public CefRequestHandler { public: - TestHandler(); + // Tracks the completion state of related test runs. + class CompletionState { + public: + // |total| is the number of times that TestComplete() must be called before + // WaitForTests() will return. + explicit CompletionState(int total); + + // Call this method to indicate that a test has completed. + void TestComplete(); + + // This method blocks until TestComplete() has been called the required + // number of times. + void WaitForTests(); + + int total() const { return total_; } + int count() const { return count_; } + + private: + int total_; + int count_; + + // Handle used to notify when the test is complete + base::WaitableEvent event_; + }; + + // Represents a collection of related tests that need to be run + // simultaniously. + class Collection { + public: + // The |completion_state| object must outlive this class. + explicit Collection(CompletionState* completion_state); + + // The |test_handler| object must outlive this class and it must share the + // same CompletionState object passed to the constructor. + void AddTestHandler(TestHandler* test_handler); + + // Manages the test run. + // 1. Calls TestHandler::SetupTest() for all of the test objects. + // 2. Waits for all TestHandler objects to report that initial setup is + // complete by calling TestHandler::SetupComplete(). + // 3. Calls TestHandler::RunTest() for all of the test objects. + // 4. Waits for all TestHandler objects to report that the test is + // complete by calling TestHandler::DestroyTest(). + void ExecuteTests(); + + private: + CompletionState* completion_state_; + + typedef std::list TestHandlerList; + TestHandlerList handler_list_; + }; + + // The |completion_state| object if specified must outlive this class. + explicit TestHandler(CompletionState* completion_state = NULL); virtual ~TestHandler(); - // Implement this method to run the test + // Implement this method to set up the test. Only used in combination with a + // Collection. Call SetupComplete() once the setup is complete. + virtual void SetupTest() {} + + // Implement this method to run the test. Call DestroyTest() once the test is + // complete. virtual void RunTest() =0; // CefClient methods. Add new methods as needed by test cases. @@ -92,15 +152,20 @@ class TestHandler : public CefClient, CefRefPtr GetBrowser() { return browser_; } int GetBrowserId() { return browser_id_; } - // Called by the test function to execute the test. This method blocks until + // Called by the test function to execute the test. This method blocks until // the test is complete. Do not reference the object after this method - // returns. + // returns. Do not use this method if the CompletionState object is shared by + // multiple handlers or when using a Collection object. void ExecuteTest(); // Returns true if a browser currently exists. static bool HasBrowser() { return browser_count_ > 0; } protected: + // Indicate that test setup is complete. Only used in combination with a + // Collection. + virtual void SetupComplete(); + // Destroy the browser window. Once the window is destroyed test completion // will be signaled. virtual void DestroyTest(); @@ -119,8 +184,9 @@ class TestHandler : public CefClient, // The browser window identifier int browser_id_; - // Handle used to notify when the test is complete - base::WaitableEvent completion_event_; + // Used to notify when the test is complete + CompletionState* completion_state_; + bool completion_state_owned_; // Map of resources that can be automatically loaded typedef std::map >