diff --git a/cef.gyp b/cef.gyp index fe7c21702..33c021793 100644 --- a/cef.gyp +++ b/cef.gyp @@ -403,6 +403,7 @@ 'tests/unittests/display_unittest.cc', 'tests/unittests/dom_unittest.cc', 'tests/unittests/download_unittest.cc', + 'tests/unittests/frame_unittest.cc', 'tests/unittests/geolocation_unittest.cc', 'tests/unittests/jsdialog_unittest.cc', 'tests/unittests/life_span_unittest.cc', @@ -1478,6 +1479,7 @@ 'tests/unittests/client_app_delegates.cc', 'tests/unittests/cookie_unittest.cc', 'tests/unittests/dom_unittest.cc', + 'tests/unittests/frame_unittest.cc', 'tests/unittests/message_router_unittest.cc', 'tests/unittests/navigation_unittest.cc', 'tests/unittests/process_message_unittest.cc', diff --git a/include/cef_browser.h b/include/cef_browser.h index 763dfaa36..a067afe43 100644 --- a/include/cef_browser.h +++ b/include/cef_browser.h @@ -159,7 +159,7 @@ class CefBrowser : public virtual CefBase { /// // Returns the frame with the specified name, or NULL if not found. /// - /*--cef()--*/ + /*--cef(optional_param=name)--*/ virtual CefRefPtr GetFrame(const CefString& name) =0; /// diff --git a/libcef/browser/frame_host_impl.cc b/libcef/browser/frame_host_impl.cc index 2cf4905bf..9a1c8f114 100644 --- a/libcef/browser/frame_host_impl.cc +++ b/libcef/browser/frame_host_impl.cc @@ -56,7 +56,7 @@ CefFrameHostImpl::CefFrameHostImpl(CefBrowserHostImpl* browser, : frame_id_(frame_id), is_main_frame_(is_main_frame), browser_(browser), - is_focused_(false), + is_focused_(is_main_frame_), // The main frame always starts focused. url_(url), name_(name), parent_frame_id_(parent_frame_id == kUnspecifiedFrameId ? diff --git a/libcef/renderer/browser_impl.cc b/libcef/renderer/browser_impl.cc index 96c181443..7d94e4821 100644 --- a/libcef/renderer/browser_impl.cc +++ b/libcef/renderer/browser_impl.cc @@ -190,9 +190,17 @@ CefRefPtr CefBrowserImpl::GetFrame(int64 identifier) { CefRefPtr CefBrowserImpl::GetFrame(const CefString& name) { CEF_REQUIRE_RT_RETURN(NULL); - if (render_view()->GetWebView()) { - WebFrame* frame = - render_view()->GetWebView()->findFrameByName(name.ToString16()); + blink::WebView* web_view = render_view()->GetWebView(); + if (web_view) { + const blink::WebString& frame_name = name.ToString16(); + // Search by assigned frame name (Frame::name). + WebFrame* frame = web_view->findFrameByName(frame_name, + web_view->mainFrame()); + if (!frame) { + // Search by unique frame name (Frame::uniqueName). + frame = webkit_glue::FindFrameByUniqueName(frame_name, + web_view->mainFrame()); + } if (frame) return GetWebFrameImpl(frame).get(); } @@ -222,6 +230,9 @@ size_t CefBrowserImpl::GetFrameCount() { void CefBrowserImpl::GetFrameIdentifiers(std::vector& identifiers) { CEF_REQUIRE_RT_RETURN_VOID(); + if (identifiers.size() > 0) + identifiers.clear(); + if (render_view()->GetWebView()) { WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); if (main_frame) { @@ -237,6 +248,9 @@ void CefBrowserImpl::GetFrameIdentifiers(std::vector& identifiers) { void CefBrowserImpl::GetFrameNames(std::vector& names) { CEF_REQUIRE_RT_RETURN_VOID(); + if (names.size() > 0) + names.clear(); + if (render_view()->GetWebView()) { WebFrame* main_frame = render_view()->GetWebView()->mainFrame(); if (main_frame) { diff --git a/libcef/renderer/webkit_glue.cc b/libcef/renderer/webkit_glue.cc index d24eecd75..bd29178f4 100644 --- a/libcef/renderer/webkit_glue.cc +++ b/libcef/renderer/webkit_glue.cc @@ -26,6 +26,7 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "third_party/WebKit/public/web/WebViewClient.h" #include "third_party/WebKit/Source/core/dom/Node.h" +#include "third_party/WebKit/Source/web/WebLocalFrameImpl.h" #include "third_party/WebKit/Source/web/WebViewImpl.h" MSVC_POP_WARNING(); #undef LOG @@ -94,4 +95,30 @@ int64 GetIdentifier(blink::WebFrame* frame) { return kInvalidFrameId; } +// Based on WebViewImpl::findFrameByName and FrameTree::find. +blink::WebFrame* FindFrameByUniqueName(const blink::WebString& unique_name, + blink::WebFrame* relative_to_frame) { + WebCore::Frame* start_frame = toWebLocalFrameImpl(relative_to_frame)->frame(); + if (!start_frame) + return NULL; + + const AtomicString& atomic_name = unique_name; + WebCore::Frame* found_frame = NULL; + + // Search the subtree starting with |start_frame|. + for (WebCore::Frame* frame = start_frame; + frame; + frame = frame->tree().traverseNext(start_frame)) { + if (frame->tree().uniqueName() == atomic_name) { + found_frame = frame; + break; + } + } + + if (found_frame && found_frame->isLocalFrame()) + return blink::WebLocalFrameImpl::fromFrame(toLocalFrame(found_frame)); + + return NULL; +} + } // webkit_glue diff --git a/libcef/renderer/webkit_glue.h b/libcef/renderer/webkit_glue.h index 479d81099..ae6be4e0a 100644 --- a/libcef/renderer/webkit_glue.h +++ b/libcef/renderer/webkit_glue.h @@ -38,6 +38,11 @@ bool SetNodeValue(blink::WebNode& node, const blink::WebString& value); int64 GetIdentifier(blink::WebFrame* frame); +// Find the frame with the specified |unique_name| relative to +// |relative_to_frame| in the frame hierarchy. +blink::WebFrame* FindFrameByUniqueName(const blink::WebString& unique_name, + blink::WebFrame* relative_to_frame); + } // webkit_glue #endif // CEF_LIBCEF_RENDERER_WEBKIT_GLUE_H_ diff --git a/libcef_dll/cpptoc/browser_cpptoc.cc b/libcef_dll/cpptoc/browser_cpptoc.cc index 3844a556b..ff8000e62 100644 --- a/libcef_dll/cpptoc/browser_cpptoc.cc +++ b/libcef_dll/cpptoc/browser_cpptoc.cc @@ -247,10 +247,7 @@ struct _cef_frame_t* CEF_CALLBACK browser_get_frame(struct _cef_browser_t* self, DCHECK(self); if (!self) return NULL; - // Verify param: name; type: string_byref_const - DCHECK(name); - if (!name) - return NULL; + // Unverified params: name // Execute CefRefPtr _retval = CefBrowserCppToC::Get(self)->GetFrame( diff --git a/libcef_dll/ctocpp/browser_ctocpp.cc b/libcef_dll/ctocpp/browser_ctocpp.cc index bfe707b44..bab4d979d 100644 --- a/libcef_dll/ctocpp/browser_ctocpp.cc +++ b/libcef_dll/ctocpp/browser_ctocpp.cc @@ -226,10 +226,7 @@ CefRefPtr CefBrowserCToCpp::GetFrame(const CefString& name) { // AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING - // Verify param: name; type: string_byref_const - DCHECK(!name.empty()); - if (name.empty()) - return NULL; + // Unverified params: name // Execute cef_frame_t* _retval = struct_->get_frame(struct_, diff --git a/tests/unittests/client_app_delegates.cc b/tests/unittests/client_app_delegates.cc index 311cf1b65..c2a49cf7b 100644 --- a/tests/unittests/client_app_delegates.cc +++ b/tests/unittests/client_app_delegates.cc @@ -6,9 +6,9 @@ // static void ClientApp::CreateBrowserDelegates(BrowserDelegateSet& delegates) { - // Bring in the V8 tests. - extern void CreateV8BrowserTests(BrowserDelegateSet& delegates); - CreateV8BrowserTests(delegates); + // Bring in the Frame tests. + extern void CreateFrameBrowserTests(BrowserDelegateSet& delegates); + CreateFrameBrowserTests(delegates); // Bring in the Navigation tests. extern void CreateNavigationBrowserTests(BrowserDelegateSet& delegates); @@ -17,48 +17,56 @@ void ClientApp::CreateBrowserDelegates(BrowserDelegateSet& delegates) { // Bring in the RequestHandler tests. extern void CreateRequestHandlerBrowserTests(BrowserDelegateSet& delegates); CreateRequestHandlerBrowserTests(delegates); + + // Bring in the V8 tests. + extern void CreateV8BrowserTests(BrowserDelegateSet& delegates); + CreateV8BrowserTests(delegates); } // static void ClientApp::CreateRenderDelegates(RenderDelegateSet& delegates) { - // Bring in the routing test handler delegate. - extern void CreateRoutingTestHandlerDelegate( - ClientApp::RenderDelegateSet& delegates); - CreateRoutingTestHandlerDelegate(delegates); + // Bring in the Frame tests. + extern void CreateFrameRendererTests(RenderDelegateSet& delegates); + CreateFrameRendererTests(delegates); + + // Bring in the DOM tests. + extern void CreateDOMRendererTests(RenderDelegateSet& delegates); + CreateDOMRendererTests(delegates); // Bring in the message router tests. extern void CreateMessageRouterRendererTests( ClientApp::RenderDelegateSet& delegates); CreateMessageRouterRendererTests(delegates); + // Bring in the Navigation tests. + extern void CreateNavigationRendererTests(RenderDelegateSet& delegates); + CreateNavigationRendererTests(delegates); + // Bring in the process message tests. extern void CreateProcessMessageRendererTests( ClientApp::RenderDelegateSet& delegates); CreateProcessMessageRendererTests(delegates); - // Bring in the V8 tests. - extern void CreateV8RendererTests(RenderDelegateSet& delegates); - CreateV8RendererTests(delegates); - - // Bring in the DOM tests. - extern void CreateDOMRendererTests(RenderDelegateSet& delegates); - CreateDOMRendererTests(delegates); - - // Bring in the URLRequest tests. - extern void CreateURLRequestRendererTests(RenderDelegateSet& delegates); - CreateURLRequestRendererTests(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); // Bring in the Request tests. extern void CreateRequestRendererTests(RenderDelegateSet& delegates); CreateRequestRendererTests(delegates); - // Bring in the RequestHandler tests. - extern void CreateRequestHandlerRendererTests(RenderDelegateSet& delegates); - CreateRequestHandlerRendererTests(delegates); + // Bring in the routing test handler delegate. + extern void CreateRoutingTestHandlerDelegate( + ClientApp::RenderDelegateSet& delegates); + CreateRoutingTestHandlerDelegate(delegates); + + // Bring in the URLRequest tests. + extern void CreateURLRequestRendererTests(RenderDelegateSet& delegates); + CreateURLRequestRendererTests(delegates); + + // Bring in the V8 tests. + extern void CreateV8RendererTests(RenderDelegateSet& delegates); + CreateV8RendererTests(delegates); } // static diff --git a/tests/unittests/frame_unittest.cc b/tests/unittests/frame_unittest.cc new file mode 100644 index 000000000..4076621dd --- /dev/null +++ b/tests/unittests/frame_unittest.cc @@ -0,0 +1,2336 @@ +// Copyright (c) 2014 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 this first to avoid type conflicts with CEF headers. +#include "tests/unittests/chromium_includes.h" + +#include "include/base/cef_bind.h" +#include "include/base/cef_scoped_ptr.h" +#include "include/wrapper/cef_closure_task.h" +#include "include/wrapper/cef_stream_resource_handler.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "tests/cefclient/client_app.h" +#include "tests/unittests/test_handler.h" + +// Comment out this define to disable the unit test timeout. +#define TIMEOUT_ENABLED 1 + +namespace { + +// The frame navigation test harness work as follows: +// +// In the browser process: +// 1. TEST() function creates a new FrameNavTestHandler instance with a unique +// FrameNavFactoryId. +// 2. FrameNavTestHandler calls FrameNavExpectationsFactoryBrowser::FromID to +// create a new factory instance. +// 3. FrameNavTestHandler calls FrameNavExpectationsFactoryBrowser::Create to +// create a new FrameNavExpectationsBrowser instance for the current +// navigation. +// 4. FrameNavTestHandler retrieves the URL to load via +// FrameNavExpectationsBrowser::GetMainURL and calls either CreateBrowser +// (for the first navigation) or LoadURL (for the following navigations). +// 5. If the renderer process does not already exist CEF creates it with +// command-line arguments that specify the FrameNavFactoryId via +// FrameNavBrowserTest::OnBeforeChildProcessLaunch. +// +// In the renderer process: +// 6. If the renderer process is newly created FrameNavRendererTest calls +// FrameNavExpectationsFactoryRenderer::FromID to create a new factory +// instance. +// 7. FrameNavRendererTest calls FrameNavExpectationsFactoryRenderer::Create to +// create a new FrameNavExpectationsRenderer instance for the current +// navigation. +// +// In both processes: +// 8. Callback notifications are sent to the FrameNavExpectations* instances. +// +// In the renderer process: +// 9. When the FrameNavExpectationsRenderer instance determines that the +// renderer side of the test is complete it calls SignalComplete which +// finalizes and deletes the FrameNavExpectationsRenderer instance and +// sends an IPC message to the browser process. +// +// In the browser process: +// 11.FrameNavExpectationsBrowser::OnRendererComplete is called in response to +// renderer-side test completion message. +// 12.When the FrameNavExpectationsBrowser instance determines that the browser +// side of the test is complete it calls SignalComplete which finalizes and +// deletes the FrameNavExpectationsBrowser instance. +// 13.If FrameNavExpectationsFactoryBrowser::HasMoreNavigations returns false +// then DestroyTest is called and the test ends. Otherwise, the navigation +// count is incremented and the process repeats starting with step #3. +// +// +// To add a new test case: +// 1. Add a new value to the FrameNavFactoryId enumeration. +// 2. Provide implementations of FrameNavExpectations*. +// 3. Add a case for the new factory ID to FrameNavExpectationsFactory*::FromID. +// 4. Implement a TEST() function that creates a FrameNavTestHandler instance +// and passes the new factory ID. +// +// +// Run with the `--single-process` command-line flag to see expectation failures +// from the renderer process. +// + +// All known factory IDs. +enum FrameNavFactoryId { + FNF_ID_INVALID = 0, + FNF_ID_SINGLE_NAV_HARNESS, + FNF_ID_SINGLE_NAV, + FNF_ID_MULTI_NAV_HARNESS, + FNF_ID_MULTI_NAV, + FNF_ID_NESTED_IFRAMES_SAME_ORIGIN, + FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN, +}; + +// Command-line argument names. +const char kTestArg[] = "test"; +const char kTestFactoryIdArg[] = "testfid"; + +// IPC message name. +const char kFrameNavMsg[] = "FrameTest.Navigation"; + +// Origins used in tests. +const char kFrameNavOrigin0[] = "http://tests-framenav0.com/"; +const char kFrameNavOrigin1[] = "http://tests-framenav1.com/"; +const char kFrameNavOrigin2[] = "http://tests-framenav2.com/"; + +// Maximum number of navigations. Should be kept synchronized with the number +// of kFrameNavOrigin* values. Don't modify this value without checking the +// below use cases. +const int kMaxMultiNavNavigations = 3; + +// Global variables identifying the currently running test. +bool g_frame_nav_test = false; +FrameNavFactoryId g_frame_nav_factory_id = FNF_ID_INVALID; + + +// Abstract base class representing expectations that result from a navigation. +class FrameNavExpectations { + public: + typedef base::Callback)> CompletionCallback; + + FrameNavExpectations(int nav, bool renderer) + : nav_(nav), + renderer_(renderer) { + } + virtual ~FrameNavExpectations() {} + + // Browser and renderer notifications. + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) { return true; } + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) { return true; } + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) { return true; } + + // Final expectations check before this object is deleted. + virtual bool Finalize() =0; + + // Signal that all expectations are completed. Should be called as a result of + // notifications. + void SignalComplete(CefRefPtr browser) { + if (!completion_callback_.is_null()) { + // Execute the callback asynchronously to avoid any issues with what's + // currently on the stack. + CefPostTask((renderer_ ? TID_RENDERER: TID_UI), + base::Bind(completion_callback_, browser)); + completion_callback_.Reset(); + } + } + + // Returns the current navigation count. In the browser process this value + // increments over the life span of the FrameNavTestHandler instance. In the + // renderer process this value increments over the life span of a single + // renderer instance (i.e. cross-origin navigations will cause this value to + // reset). + int nav() const { return nav_; } + + // Returns true if this is a renderer-side expectation object. + bool renderer() const { return renderer_; } + + void set_completion_callback(const CompletionCallback& completion_callback) { + completion_callback_ = completion_callback; + } + + private: + int nav_; + bool renderer_; + CompletionCallback completion_callback_; +}; + +// Browser process expectations abstact base class. +class FrameNavExpectationsBrowser : public FrameNavExpectations { + public: + explicit FrameNavExpectationsBrowser(int nav) + : FrameNavExpectations(nav, false) { + } + + // Loading information. + virtual std::string GetMainURL() =0; + virtual std::string GetContentForURL(const std::string& url) =0; + + // Browser-only notifications. + virtual bool OnAfterCreated(CefRefPtr browser) { return true; } + virtual bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame) { return true; } + virtual bool GetResourceHandler(CefRefPtr browser, + CefRefPtr frame) { return true; } + + // Called when the renderer signals completion. + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) =0; +}; + +// Renderer process expectations abstract base class. +class FrameNavExpectationsRenderer : public FrameNavExpectations { + public: + explicit FrameNavExpectationsRenderer(int nav) + : FrameNavExpectations(nav, true) { + } + + // Renderer-only notifications. + virtual bool OnBeforeNavigation(CefRefPtr browser, + CefRefPtr frame) { return true; } +}; + + +// Abstract base class for the factory that creates expectations objects. +class FrameNavExpectationsFactory { + public: + FrameNavExpectationsFactory() {} + virtual ~FrameNavExpectationsFactory() {} + + // Returns the unique ID for this factory type. + virtual FrameNavFactoryId GetID() const =0; +}; + +// Browser process expectations factory abstact base class. +class FrameNavExpectationsFactoryBrowser : public FrameNavExpectationsFactory { + public: + FrameNavExpectationsFactoryBrowser() {} + + // Create a new factory instance of the specified type. + static scoped_ptr + FromID(FrameNavFactoryId id); + + // Returns true if there will be more navigations in the browser process + // handler. + virtual bool HasMoreNavigations() const =0; + + // Verify final expectations results. + virtual bool Finalize() =0; + + scoped_ptr Create( + int nav, + const FrameNavExpectations::CompletionCallback& completion_callback) { + scoped_ptr expectations; + expectations = Create(nav); + expectations->set_completion_callback(completion_callback); + return expectations.Pass(); + } + + protected: + // Implement in the test-specific factory instance. + virtual scoped_ptr Create(int nav) =0; +}; + +// Renderer process expectations factory abstact base class. +class FrameNavExpectationsFactoryRenderer : public FrameNavExpectationsFactory { + public: + FrameNavExpectationsFactoryRenderer() {} + + // Create a new factory instance of the specified type. + static scoped_ptr + FromID(FrameNavFactoryId id); + + scoped_ptr Create( + int nav, + const FrameNavExpectations::CompletionCallback& completion_callback) { + scoped_ptr expectations; + expectations = Create(nav); + expectations->set_completion_callback(completion_callback); + return expectations.Pass(); + } + + protected: + // Implement in the test-specific factory instance. + virtual scoped_ptr Create(int nav) =0; +}; + + +// Browser side app delegate. +class FrameNavBrowserTest : public ClientApp::BrowserDelegate { + public: + FrameNavBrowserTest() {} + + virtual void OnBeforeChildProcessLaunch( + CefRefPtr app, + CefRefPtr command_line) OVERRIDE { + if (!g_frame_nav_test) + return; + + std::stringstream ss; + ss << g_frame_nav_factory_id; + + // Indicate to the render process that the test should be run. + command_line->AppendSwitchWithValue(kTestArg, kFrameNavMsg); + command_line->AppendSwitchWithValue(kTestFactoryIdArg, ss.str()); + } + + protected: + IMPLEMENT_REFCOUNTING(FrameNavBrowserTest); +}; + +// Renderer side handler. +class FrameNavRendererTest : public ClientApp::RenderDelegate, + public CefLoadHandler { + public: + FrameNavRendererTest() + : run_test_(false), + nav_(0) {} + + virtual void OnRenderThreadCreated( + CefRefPtr app, + CefRefPtr extra_info) OVERRIDE { + // The g_* values will be set when running in single-process mode. + if (!g_frame_nav_test) { + // Check that the test should be run. + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + const std::string& test = command_line->GetSwitchValue(kTestArg); + if (test != kFrameNavMsg) + return; + } + + FrameNavFactoryId factory_id = g_frame_nav_factory_id; + if (factory_id == FNF_ID_INVALID) { + // Retrieve the factory ID from the command-line. + CefRefPtr command_line = + CefCommandLine::GetGlobalCommandLine(); + if (command_line->HasSwitch(kTestFactoryIdArg)) { + factory_id = static_cast( + atoi(command_line->GetSwitchValue( + kTestFactoryIdArg).ToString().c_str())); + if (factory_id == FNF_ID_INVALID) + return; + } + } + + run_test_ = true; + factory_ = FrameNavExpectationsFactoryRenderer::FromID(factory_id); + } + + virtual CefRefPtr GetLoadHandler( + CefRefPtr app) OVERRIDE { + if (!run_test_) + return NULL; + + return this; + } + + virtual void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) OVERRIDE { + CreateExpectationsIfNecessary(); + EXPECT_TRUE(expectations_->OnLoadingStateChange(browser, isLoading)) << + "isLoading = " << isLoading << ", nav = " << nav_; + } + + virtual void OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + CreateExpectationsIfNecessary(); + EXPECT_TRUE(expectations_->OnLoadStart(browser, frame)) << "nav = " << nav_; + } + + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + CreateExpectationsIfNecessary(); + EXPECT_TRUE(expectations_->OnLoadEnd(browser, frame)) << "nav = " << nav_; + } + + virtual bool OnBeforeNavigation(CefRefPtr app, + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + cef_navigation_type_t navigation_type, + bool is_redirect) OVERRIDE { + if (!run_test_) + return false; + + CreateExpectationsIfNecessary(); + EXPECT_TRUE(expectations_->OnBeforeNavigation(browser, frame)) << + "nav = " << nav_; + return false; + } + + protected: + // Create a new expectations object if one does not already exist for the + // current navigation. + void CreateExpectationsIfNecessary() { + if (expectations_) + return; + expectations_ = factory_->Create( + nav_, + base::Bind(&FrameNavRendererTest::SendTestResults, this)); + } + + // Send the test results. + // Will be called via FrameNavExpectations::SignalComplete. + void SendTestResults(CefRefPtr browser) { + // End of the current expectations object. + EXPECT_TRUE(expectations_->Finalize()) << "nav = " << nav_; + expectations_.reset(NULL); + + // Check if the test has failed. + bool result = !TestFailed(); + + // Return the result to the browser process. + CefRefPtr return_msg = + CefProcessMessage::Create(kFrameNavMsg); + CefRefPtr args = return_msg->GetArgumentList(); + EXPECT_TRUE(args.get()); + EXPECT_TRUE(args->SetInt(0, nav_)); + EXPECT_TRUE(args->SetBool(1, result)); + EXPECT_TRUE(browser->SendProcessMessage(PID_BROWSER, return_msg)); + + nav_++; + } + + bool run_test_; + int nav_; + scoped_ptr factory_; + scoped_ptr expectations_; + + IMPLEMENT_REFCOUNTING(FrameNavRendererTest); +}; + +// Browser side handler. +class FrameNavTestHandler : public TestHandler { + public: + explicit FrameNavTestHandler(FrameNavFactoryId factory_id) + : nav_(0), + factory_(FrameNavExpectationsFactoryBrowser::FromID(factory_id)) +#if defined(TIMEOUT_ENABLED) + , weak_ptr_factory_(this) +#endif + { + EXPECT_FALSE(g_frame_nav_test); + EXPECT_EQ(FNF_ID_INVALID, g_frame_nav_factory_id); + g_frame_nav_test = true; + g_frame_nav_factory_id = factory_id; + } + + virtual ~FrameNavTestHandler() { + EXPECT_TRUE(got_destroyed_); + g_frame_nav_test = false; + g_frame_nav_factory_id = FNF_ID_INVALID; + } + + virtual void RunTest() OVERRIDE { + // Create the first expectations object. + expectations_ = factory_->Create( + nav_, + base::Bind(&FrameNavTestHandler::RunNextNav, this)); + + // Create the browser with the initial URL. + CreateBrowser(expectations_->GetMainURL()); + +#if defined(TIMEOUT_ENABLED) + // Time out the test after a reasonable period of time. + CefPostDelayedTask(TID_UI, + base::Bind(&FrameNavTestHandler::DestroyTest, + weak_ptr_factory_.GetWeakPtr()), + 4000); +#endif + } + + // Transition to the next navigation. + // Will be called via FrameNavExpectations::SignalComplete. + void RunNextNav(CefRefPtr browser) { + // End of the current expectations object. + EXPECT_TRUE(expectations_->Finalize()); + expectations_.reset(NULL); + + if (!factory_->HasMoreNavigations()) { + // End of the test. + DestroyTest(); + return; + } + + nav_++; + + // Create the next expectations object. + expectations_ = factory_->Create( + nav_, + base::Bind(&FrameNavTestHandler::RunNextNav, this)); + + // Load the main URL. + browser->GetMainFrame()->LoadURL(expectations_->GetMainURL()); + } + + virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE { + TestHandler::OnAfterCreated(browser); + + EXPECT_TRUE(expectations_->OnAfterCreated(browser)) << "nav = " << nav_; + } + + virtual CefRefPtr GetResourceHandler( + CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request) OVERRIDE { + EXPECT_TRUE(expectations_->GetResourceHandler(browser, frame)) << + "nav = " << nav_; + + const std::string& url = request->GetURL(); + const std::string& content = expectations_->GetContentForURL(url); + EXPECT_TRUE(!content.empty()) << "nav = " << nav_; + + CefRefPtr stream = + CefStreamReader::CreateForData( + static_cast(const_cast(content.c_str())), + content.length()); + return new CefStreamResourceHandler("text/html", stream); + } + + virtual bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + bool is_redirect) OVERRIDE { + EXPECT_TRUE(expectations_->OnBeforeBrowse(browser, frame)) << + "nav = " << nav_; + + return false; + } + + virtual void OnLoadingStateChange(CefRefPtr browser, + bool isLoading, + bool canGoBack, + bool canGoForward) OVERRIDE { + EXPECT_TRUE(expectations_->OnLoadingStateChange(browser, isLoading)) << + "isLoading = " << isLoading << ", nav = " << nav_;; + } + + virtual void OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_TRUE(expectations_->OnLoadStart(browser, frame)) << "nav = " << nav_; + } + + virtual void OnLoadEnd(CefRefPtr browser, + CefRefPtr frame, + int httpStatusCode) OVERRIDE { + EXPECT_TRUE(expectations_->OnLoadEnd(browser, frame)) << "nav = " << nav_; + } + + virtual bool OnProcessMessageReceived( + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE { + if (message->GetName().ToString() == kFrameNavMsg) { + // Test that the renderer side succeeded. + CefRefPtr args = message->GetArgumentList(); + EXPECT_TRUE(args.get()); + + EXPECT_TRUE(expectations_->OnRendererComplete(browser, + args->GetInt(0), args->GetBool(1))) << "nav = " << nav_; + return true; + } + + // Message not handled. + return false; + } + + virtual void DestroyTest() OVERRIDE { + if (got_destroyed_) + return; + + got_destroyed_.yes(); + +#if defined(TIMEOUT_ENABLED) + weak_ptr_factory_.InvalidateWeakPtrs(); +#endif + + // The expectations should have been tested already. + EXPECT_FALSE(expectations_.get()); + + // Test that factory conditions we met. + EXPECT_TRUE(factory_->Finalize()) << "nav = " << nav_; + + TestHandler::DestroyTest(); + } + + int nav_; + TrackCallback got_destroyed_; + scoped_ptr factory_; + scoped_ptr expectations_; + +#if defined(TIMEOUT_ENABLED) + // Must be the last member. + base::WeakPtrFactory weak_ptr_factory_; +#endif +}; + + +// Wait for the the FrameNavTestHandler destructor to be called. This avoids +// problems with setting global variables when running multiple tests. +void WaitForTestHandlerDestruction() { + while (g_frame_nav_test) + base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(100)); +} + +// Helper for defining frame tests. +#define FRAME_TEST(name, factory_id) \ + TEST(FrameTest, name) { \ + CefRefPtr handler = \ + new FrameNavTestHandler(factory_id); \ + handler->ExecuteTest(); \ + handler = NULL; \ + WaitForTestHandlerDestruction(); \ + } + + +// Browser process expectations for a single navigation. +class FrameNavExpectationsBrowserSingleNav : + public FrameNavExpectationsBrowser { + public: + explicit FrameNavExpectationsBrowserSingleNav(int nav) + : FrameNavExpectationsBrowser(nav) { + } + + virtual ~FrameNavExpectationsBrowserSingleNav() { + EXPECT_TRUE(got_finalize_); + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (isLoading) { + EXPECT_FALSE(got_loading_state_change_start_); + got_loading_state_change_start_.yes(); + } else { + EXPECT_FALSE(got_loading_state_change_end_); + got_loading_state_change_end_.yes(); + SignalCompleteIfDone(browser); + } + return true; + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_start_); + got_load_start_.yes(); + return true; + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_end_); + got_load_end_.yes(); + SignalCompleteIfDone(browser); + return true; + } + + virtual bool OnAfterCreated(CefRefPtr browser) OVERRIDE { + EXPECT_FALSE(got_after_created_); + got_after_created_.yes(); + return true; + } + + virtual bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_before_browse_); + got_before_browse_.yes(); + return true; + } + + virtual bool GetResourceHandler(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_get_resource_handler_); + got_get_resource_handler_.yes(); + return true; + } + + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) OVERRIDE { + EXPECT_EQ(nav(), renderer_nav); + EXPECT_TRUE(renderer_result); + EXPECT_FALSE(got_renderer_done_); + got_renderer_done_.yes(); + SignalCompleteIfDone(browser); + return true; + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(got_load_start_); + V_EXPECT_TRUE(got_load_end_); + V_EXPECT_TRUE(got_loading_state_change_start_); + V_EXPECT_TRUE(got_loading_state_change_end_); + V_EXPECT_TRUE(got_renderer_done_); + V_EXPECT_TRUE(got_after_created_); + V_EXPECT_TRUE(got_before_browse_); + V_EXPECT_TRUE(got_get_resource_handler_); + V_EXPECT_FALSE(got_finalize_); + + got_finalize_.yes(); + + V_RETURN(); + } + + private: + void SignalCompleteIfDone(CefRefPtr browser) { + if (got_renderer_done_ && got_load_end_ && got_loading_state_change_end_) + SignalComplete(browser); + } + + TrackCallback got_load_start_; + TrackCallback got_load_end_; + TrackCallback got_loading_state_change_start_; + TrackCallback got_loading_state_change_end_; + TrackCallback got_renderer_done_; + TrackCallback got_after_created_; + TrackCallback got_before_browse_; + TrackCallback got_get_resource_handler_; + TrackCallback got_finalize_; +}; + +// Renderer process expectations for a single navigation. +class FrameNavExpectationsRendererSingleNav : + public FrameNavExpectationsRenderer { + public: + explicit FrameNavExpectationsRendererSingleNav(int nav) + : FrameNavExpectationsRenderer(nav) { + } + + virtual ~FrameNavExpectationsRendererSingleNav() { + EXPECT_TRUE(got_finalize_); + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (isLoading) { + EXPECT_FALSE(got_loading_state_change_start_); + got_loading_state_change_start_.yes(); + } else { + EXPECT_FALSE(got_loading_state_change_end_); + got_loading_state_change_end_.yes(); + SignalCompleteIfDone(browser); + } + return true; + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_start_); + got_load_start_.yes(); + return true; + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_end_); + got_load_end_.yes(); + SignalCompleteIfDone(browser); + return true; + } + + virtual bool OnBeforeNavigation(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_before_navigation_); + got_before_navigation_.yes(); + return true; + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(got_load_start_); + V_EXPECT_TRUE(got_load_end_); + V_EXPECT_TRUE(got_loading_state_change_start_); + V_EXPECT_TRUE(got_loading_state_change_end_); + V_EXPECT_TRUE(got_before_navigation_); + V_EXPECT_FALSE(got_finalize_); + + got_finalize_.yes(); + + V_RETURN(); + } + + private: + void SignalCompleteIfDone(CefRefPtr browser) { + if (got_load_end_ && got_loading_state_change_end_) + SignalComplete(browser); + } + + TrackCallback got_load_start_; + TrackCallback got_load_end_; + TrackCallback got_loading_state_change_start_; + TrackCallback got_loading_state_change_end_; + TrackCallback got_before_navigation_; + TrackCallback got_finalize_; +}; + +// Test that the single nav harness works. +class FrameNavExpectationsBrowserTestSingleNavHarness : + public FrameNavExpectationsBrowserSingleNav { + public: + typedef FrameNavExpectationsBrowserSingleNav parent; + + explicit FrameNavExpectationsBrowserTestSingleNavHarness(int nav) + : parent(nav) { + } + + virtual ~FrameNavExpectationsBrowserTestSingleNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual std::string GetMainURL() OVERRIDE { + EXPECT_FALSE(got_get_main_url_); + got_get_main_url_.yes(); + return kFrameNavOrigin0; + } + + virtual std::string GetContentForURL(const std::string& url) OVERRIDE { + EXPECT_FALSE(got_get_content_for_url_); + got_get_content_for_url_.yes(); + EXPECT_STREQ(kFrameNavOrigin0, url.c_str()); + return "Nav"; + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + + V_DECLARE(); + V_EXPECT_TRUE(got_get_main_url_); + V_EXPECT_TRUE(got_get_content_for_url_); + V_EXPECT_TRUE(parent::Finalize()); + V_RETURN(); + } + + private: + TrackCallback got_get_main_url_; + TrackCallback got_get_content_for_url_; + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsRendererTestSingleNavHarness : + public FrameNavExpectationsRendererSingleNav { + public: + typedef FrameNavExpectationsRendererSingleNav parent; + + explicit FrameNavExpectationsRendererTestSingleNavHarness(int nav) + : parent(nav) { + } + + virtual ~FrameNavExpectationsRendererTestSingleNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + return parent::Finalize(); + } + + private: + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsFactoryBrowserTestSingleNavHarness : + public FrameNavExpectationsFactoryBrowser { + public: + FrameNavExpectationsFactoryBrowserTestSingleNavHarness() {} + + virtual ~FrameNavExpectationsFactoryBrowserTestSingleNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_SINGLE_NAV_HARNESS; + } + + virtual bool HasMoreNavigations() const OVERRIDE { + EXPECT_FALSE(got_get_browser_navigation_count_); + got_get_browser_navigation_count_.yes(); + return false; + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + + V_DECLARE(); + V_EXPECT_TRUE(got_get_browser_navigation_count_); + V_EXPECT_TRUE(got_create_); + V_RETURN(); + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + EXPECT_FALSE(got_create_); + got_create_.yes(); + return make_scoped_ptr( + new FrameNavExpectationsBrowserTestSingleNavHarness(nav)); + } + + private: + mutable TrackCallback got_get_browser_navigation_count_; + TrackCallback got_create_; + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsFactoryRendererTestSingleNavHarness : + public FrameNavExpectationsFactoryRenderer { + public: + FrameNavExpectationsFactoryRendererTestSingleNavHarness() {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_SINGLE_NAV_HARNESS; + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + return make_scoped_ptr( + new FrameNavExpectationsRendererTestSingleNavHarness(nav)); + } +}; + +} // namespace + +// Test that the single nav harness works. +FRAME_TEST(SingleNavHarness, FNF_ID_SINGLE_NAV_HARNESS) + + +namespace { + +bool VerifySingleBrowserFrame(CefRefPtr browser, + CefRefPtr frame, + bool frame_should_exist, + const std::string& expected_url) { + V_DECLARE(); + V_EXPECT_TRUE(frame); + V_EXPECT_TRUE(frame->IsValid()); + if (frame_should_exist) { + V_EXPECT_TRUE(frame->GetIdentifier() >= 0); + } else { + V_EXPECT_TRUE(frame->GetIdentifier() == -4); // kInvalidFrameId + } + V_EXPECT_TRUE(frame->IsValid()); + V_EXPECT_TRUE(frame->IsMain()); + V_EXPECT_TRUE(frame->IsFocused()); + V_EXPECT_FALSE(frame->GetParent()); + V_EXPECT_TRUE(frame->GetName().empty()); + V_EXPECT_TRUE(browser->GetIdentifier() == + frame->GetBrowser()->GetIdentifier()); + + const std::string& frame_url = frame->GetURL(); + V_EXPECT_TRUE(frame_url == expected_url) << "frame_url = " << frame_url << + ", expected_url = " << expected_url; + + V_RETURN(); +} + +bool VerifySingleBrowserFrames(CefRefPtr browser, + CefRefPtr frame, + bool frame_should_exist, + const std::string& expected_url) { + V_DECLARE(); + V_EXPECT_TRUE(browser); + + // |frame| may be NULL for callbacks that don't specify one. + if (frame) { + V_EXPECT_TRUE(VerifySingleBrowserFrame(browser, frame, + frame_should_exist, expected_url)); + } + + CefRefPtr main_frame = browser->GetMainFrame(); + V_EXPECT_TRUE(VerifySingleBrowserFrame(browser, main_frame, + frame_should_exist, expected_url)); + + CefRefPtr focused_frame = browser->GetFocusedFrame(); + V_EXPECT_TRUE(VerifySingleBrowserFrame(browser, focused_frame, + frame_should_exist, expected_url)); + + size_t frame_count = browser->GetFrameCount(); + if (frame_should_exist) { + V_EXPECT_TRUE(frame_count == 1U); + + std::vector identifiers; + browser->GetFrameIdentifiers(identifiers); + V_EXPECT_TRUE(identifiers.size() == 1U); + if (identifiers.size() == 1U) { + V_EXPECT_TRUE(identifiers[0] == main_frame->GetIdentifier()); + V_EXPECT_TRUE(identifiers[0] == focused_frame->GetIdentifier()); + } + + std::vector names; + browser->GetFrameNames(names); + V_EXPECT_TRUE(names.size() == 1U); + if (names.size() == 1U) { + V_EXPECT_TRUE(names[0].ToString() == main_frame->GetName().ToString()); + V_EXPECT_TRUE(names[0].ToString() == focused_frame->GetName().ToString()); + } + } else { + V_EXPECT_TRUE(frame_count == 0U); + } + + V_RETURN(); +} + +// Test that single navigation works. +class FrameNavExpectationsBrowserTestSingleNav : + public FrameNavExpectationsBrowserSingleNav { + public: + typedef FrameNavExpectationsBrowserSingleNav parent; + + explicit FrameNavExpectationsBrowserTestSingleNav(int nav) + : parent(nav) { + } + + virtual std::string GetMainURL() OVERRIDE { + return kFrameNavOrigin0; + } + + virtual std::string GetContentForURL(const std::string& url) OVERRIDE { + return "Nav"; + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + V_DECLARE(); + if (isLoading) { + // No frame exists before the first load. + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false, + std::string())); + } else { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + kFrameNavOrigin0)); + } + V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading)); + V_RETURN(); + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + kFrameNavOrigin0)); + V_EXPECT_TRUE(parent::OnLoadStart(browser, frame)); + V_RETURN(); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + kFrameNavOrigin0)); + V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame)); + V_RETURN(); + } + + virtual bool OnAfterCreated(CefRefPtr browser) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false, + std::string())); + V_EXPECT_TRUE(parent::OnAfterCreated(browser)); + V_RETURN(); + } + + virtual bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + std::string())); + V_EXPECT_TRUE(parent::OnBeforeBrowse(browser, frame)); + V_RETURN(); + } + + virtual bool GetResourceHandler(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + std::string())); + V_EXPECT_TRUE(parent::GetResourceHandler(browser, frame)); + V_RETURN(); + } + + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) OVERRIDE { + return parent::OnRendererComplete(browser, renderer_nav, renderer_result); + } + + virtual bool Finalize() OVERRIDE { + return parent::Finalize(); + } +}; + +class FrameNavExpectationsRendererTestSingleNav : + public FrameNavExpectationsRendererSingleNav { + public: + typedef FrameNavExpectationsRendererSingleNav parent; + + explicit FrameNavExpectationsRendererTestSingleNav(int nav) + : parent(nav) { + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + V_DECLARE(); + // A frame should always exist in the renderer process. + if (isLoading) { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + std::string())); + } else { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + kFrameNavOrigin0)); + } + V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading)); + V_RETURN(); + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + kFrameNavOrigin0)); + V_EXPECT_TRUE(parent::OnLoadStart(browser, frame)); + V_RETURN(); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + kFrameNavOrigin0)); + V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame)); + V_RETURN(); + } + + virtual bool Finalize() OVERRIDE { + return parent::Finalize(); + } +}; + +class FrameNavExpectationsFactoryBrowserTestSingleNav : + public FrameNavExpectationsFactoryBrowser { + public: + FrameNavExpectationsFactoryBrowserTestSingleNav() {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_SINGLE_NAV; + } + + virtual bool HasMoreNavigations() const OVERRIDE { + return false; + } + + virtual bool Finalize() OVERRIDE { + return true; + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + return make_scoped_ptr( + new FrameNavExpectationsBrowserTestSingleNav(nav)); + } +}; + +class FrameNavExpectationsFactoryRendererTestSingleNav : + public FrameNavExpectationsFactoryRenderer { + public: + FrameNavExpectationsFactoryRendererTestSingleNav() {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_SINGLE_NAV; + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + return make_scoped_ptr( + new FrameNavExpectationsRendererTestSingleNav(nav)); + } +}; + +} // namespace + +// Test that single navigation works. +FRAME_TEST(SingleNav, FNF_ID_SINGLE_NAV) + + +namespace { + +// Browser process expectations for a multiple navigations. +class FrameNavExpectationsBrowserMultiNav : + public FrameNavExpectationsBrowser { + public: + explicit FrameNavExpectationsBrowserMultiNav(int nav) + : FrameNavExpectationsBrowser(nav) { + } + + virtual ~FrameNavExpectationsBrowserMultiNav() { + EXPECT_TRUE(got_finalize_); + } + + // Returns true if all navigation is done. + virtual bool IsNavigationDone() const =0; + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) + SignalCompleteIfDone(browser); + return true; + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + SignalCompleteIfDone(browser); + return true; + } + + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) OVERRIDE { + EXPECT_TRUE(renderer_result); + SignalCompleteIfDone(browser); + return true; + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_FALSE(got_finalize_); + + got_finalize_.yes(); + + V_RETURN(); + } + + private: + void SignalCompleteIfDone(CefRefPtr browser) { + if (IsNavigationDone()) + SignalComplete(browser); + } + + TrackCallback got_finalize_; +}; + +// Renderer process expectations for a multiple navigations. +class FrameNavExpectationsRendererMultiNav : + public FrameNavExpectationsRenderer { + public: + explicit FrameNavExpectationsRendererMultiNav(int nav) + : FrameNavExpectationsRenderer(nav) { + } + + virtual ~FrameNavExpectationsRendererMultiNav() { + EXPECT_TRUE(got_finalize_); + } + + // Returns true if all navigation is done. + virtual bool IsNavigationDone() const =0; + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) + SignalCompleteIfDone(browser); + return true; + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + SignalCompleteIfDone(browser); + return true; + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_FALSE(got_finalize_); + + got_finalize_.yes(); + + V_RETURN(); + } + + private: + void SignalCompleteIfDone(CefRefPtr browser) { + if (IsNavigationDone()) + SignalComplete(browser); + } + + TrackCallback got_finalize_; +}; + + +// Create a URL containing the nav number. +std::string GetMultiNavURL(const std::string& origin, int nav) { + std::stringstream ss; + ss << origin << "nav" << nav << ".html"; + return ss.str(); +} + +// Extract the nav number from the URL. +int GetNavFromMultiNavURL(const std::string& url) { + const int start = url.find("/nav"); + const int end = url.find(".html", start); + EXPECT_TRUE(start < end && start > 0); + const std::string& nav = url.substr(start + 4, end - start - 4); + return atoi(nav.c_str()); +} + +// Extract the origin from the URL. +std::string GetOriginFromMultiNavURL(const std::string& url) { + const int pos = url.rfind("/"); + EXPECT_TRUE(pos > 0); + return url.substr(0, pos + 1); +} + +// Test that the multi nav harness works. +class FrameNavExpectationsBrowserTestMultiNavHarness : + public FrameNavExpectationsBrowserMultiNav { + public: + typedef FrameNavExpectationsBrowserMultiNav parent; + + explicit FrameNavExpectationsBrowserTestMultiNavHarness(int nav) + : parent(nav), + navigation_done_count_(0) { + } + + virtual ~FrameNavExpectationsBrowserTestMultiNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual std::string GetMainURL() OVERRIDE { + EXPECT_FALSE(got_get_main_url_); + got_get_main_url_.yes(); + return GetMultiNavURL(kFrameNavOrigin0, nav()); + } + + virtual std::string GetContentForURL(const std::string& url) OVERRIDE { + EXPECT_FALSE(got_get_content_for_url_); + got_get_content_for_url_.yes(); + EXPECT_STREQ(GetMultiNavURL(kFrameNavOrigin0, nav()).c_str(), url.c_str()); + return "Nav"; + } + + virtual bool IsNavigationDone() const OVERRIDE { + navigation_done_count_++; + return got_load_state_change_done_ && got_load_end_ && + got_renderer_complete_; + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) { + EXPECT_FALSE(got_load_state_change_done_); + got_load_state_change_done_.yes(); + } + return parent::OnLoadingStateChange(browser, isLoading); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_end_); + got_load_end_.yes(); + return parent::OnLoadEnd(browser, frame); + } + + virtual bool OnAfterCreated(CefRefPtr browser) OVERRIDE { + EXPECT_FALSE(got_on_after_created_); + got_on_after_created_.yes(); + return parent::OnAfterCreated(browser); + } + + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) OVERRIDE { + EXPECT_FALSE(got_renderer_complete_); + got_renderer_complete_.yes(); + EXPECT_EQ(nav(), renderer_nav); + return parent::OnRendererComplete(browser, renderer_nav, renderer_result); + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + + V_DECLARE(); + V_EXPECT_TRUE(got_get_main_url_); + V_EXPECT_TRUE(got_get_content_for_url_); + V_EXPECT_TRUE(got_load_state_change_done_); + V_EXPECT_TRUE(got_load_end_); + if (nav() == 0) { + V_EXPECT_TRUE(got_on_after_created_); + } else { + V_EXPECT_FALSE(got_on_after_created_); + } + V_EXPECT_TRUE(got_renderer_complete_); + V_EXPECT_TRUE(navigation_done_count_ == 3); + V_EXPECT_TRUE(parent::Finalize()); + V_RETURN(); + } + + private: + TrackCallback got_get_main_url_; + TrackCallback got_get_content_for_url_; + TrackCallback got_load_state_change_done_; + TrackCallback got_load_end_; + TrackCallback got_on_after_created_; + TrackCallback got_renderer_complete_; + mutable int navigation_done_count_; + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsRendererTestMultiNavHarness : + public FrameNavExpectationsRendererMultiNav { + public: + typedef FrameNavExpectationsRendererMultiNav parent; + + explicit FrameNavExpectationsRendererTestMultiNavHarness(int nav) + : parent(nav), + navigation_done_count_(0) { + } + + virtual ~FrameNavExpectationsRendererTestMultiNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual bool IsNavigationDone() const OVERRIDE { + navigation_done_count_++; + return got_load_state_change_done_ && got_load_end_; + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) { + EXPECT_FALSE(got_load_state_change_done_); + got_load_state_change_done_.yes(); + } + return parent::OnLoadingStateChange(browser, isLoading); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + EXPECT_FALSE(got_load_end_); + got_load_end_.yes(); + return parent::OnLoadEnd(browser, frame); + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + + V_DECLARE(); + V_EXPECT_TRUE(got_load_state_change_done_); + V_EXPECT_TRUE(got_load_end_); + V_EXPECT_TRUE(navigation_done_count_ == 2); + V_EXPECT_TRUE(parent::Finalize()); + V_RETURN(); + } + + private: + TrackCallback got_load_state_change_done_; + TrackCallback got_load_end_; + mutable int navigation_done_count_; + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsFactoryBrowserTestMultiNavHarness : + public FrameNavExpectationsFactoryBrowser { + public: + FrameNavExpectationsFactoryBrowserTestMultiNavHarness() + : get_browser_navigation_count_(0), + create_count_(0) {} + + virtual ~FrameNavExpectationsFactoryBrowserTestMultiNavHarness() { + EXPECT_TRUE(got_finalize_); + } + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_MULTI_NAV_HARNESS; + } + + virtual bool HasMoreNavigations() const OVERRIDE { + get_browser_navigation_count_++; + return (get_browser_navigation_count_ < kMaxMultiNavNavigations); + } + + virtual bool Finalize() OVERRIDE { + EXPECT_FALSE(got_finalize_); + got_finalize_.yes(); + + V_DECLARE(); + V_EXPECT_TRUE(get_browser_navigation_count_ == kMaxMultiNavNavigations); + V_EXPECT_TRUE(create_count_ == kMaxMultiNavNavigations); + V_RETURN(); + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + create_count_++; + return make_scoped_ptr( + new FrameNavExpectationsBrowserTestMultiNavHarness(nav)); + } + + private: + mutable int get_browser_navigation_count_; + int create_count_; + TrackCallback got_finalize_; +}; + +class FrameNavExpectationsFactoryRendererTestMultiNavHarness : + public FrameNavExpectationsFactoryRenderer { + public: + FrameNavExpectationsFactoryRendererTestMultiNavHarness() {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_MULTI_NAV_HARNESS; + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + return make_scoped_ptr( + new FrameNavExpectationsRendererTestMultiNavHarness(nav)); + } +}; + +} // namespace + +// Test that the multiple nav harness works. +FRAME_TEST(MultiNavHarness, FNF_ID_MULTI_NAV_HARNESS) + + +namespace { + +// Test that multiple navigation works. +class FrameNavExpectationsBrowserTestMultiNav : + public FrameNavExpectationsBrowserMultiNav { + public: + typedef FrameNavExpectationsBrowserMultiNav parent; + + explicit FrameNavExpectationsBrowserTestMultiNav(int nav) + : parent(nav) { + } + + virtual std::string GetMainURL() OVERRIDE { + return GetMultiNavURL(kFrameNavOrigin0, nav()); + } + + virtual std::string GetContentForURL(const std::string& url) OVERRIDE { + return "Nav"; + } + + virtual bool IsNavigationDone() const OVERRIDE { + return got_load_state_change_done_ && got_load_end_ && + got_renderer_complete_; + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) + got_load_state_change_done_.yes(); + V_DECLARE(); + // A frame should exist in all cases except for the very first load. + if (isLoading && nav() == 0) { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false, + std::string())); + } else if (isLoading) { + // Expect the URL from the previous load. + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + GetPreviousMainURL())); + } else { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + GetMainURL())); + } + V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading)); + V_RETURN(); + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + GetMainURL())); + V_EXPECT_TRUE(parent::OnLoadStart(browser, frame)); + V_RETURN(); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + got_load_end_.yes(); + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + GetMainURL())); + V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame)); + V_RETURN(); + } + + virtual bool OnAfterCreated(CefRefPtr browser) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false, + std::string())); + V_EXPECT_TRUE(parent::OnAfterCreated(browser)); + V_RETURN(); + } + + virtual bool OnBeforeBrowse(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + std::string expected_url; + if (nav() > 0) + expected_url = GetPreviousMainURL(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + expected_url)); + V_EXPECT_TRUE(parent::OnBeforeBrowse(browser, frame)); + V_RETURN(); + } + + virtual bool GetResourceHandler(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + std::string expected_url; + if (nav() > 0) + expected_url = GetPreviousMainURL(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + expected_url)); + V_EXPECT_TRUE(parent::GetResourceHandler(browser, frame)); + V_RETURN(); + } + + virtual bool OnRendererComplete(CefRefPtr browser, + int renderer_nav, + bool renderer_result) OVERRIDE { + got_renderer_complete_.yes(); + V_DECLARE(); + V_EXPECT_TRUE(nav() == renderer_nav); + V_EXPECT_TRUE(parent::OnRendererComplete(browser, renderer_nav, + renderer_result)); + V_RETURN(); + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(got_load_state_change_done_); + V_EXPECT_TRUE(got_load_end_); + V_EXPECT_TRUE(got_renderer_complete_); + V_EXPECT_TRUE(parent::Finalize()); + V_RETURN(); + } + + private: + // Helper for VerifySingleBrowserFrames. + std::string GetPreviousMainURL() { + EXPECT_GT(nav(), 0); + return GetMultiNavURL(kFrameNavOrigin0, nav() - 1); + } + + TrackCallback got_load_state_change_done_; + TrackCallback got_load_end_; + TrackCallback got_renderer_complete_; +}; + +class FrameNavExpectationsRendererTestMultiNav : + public FrameNavExpectationsRendererMultiNav { + public: + typedef FrameNavExpectationsRendererMultiNav parent; + + explicit FrameNavExpectationsRendererTestMultiNav(int nav) + : parent(nav) { + } + + virtual bool IsNavigationDone() const OVERRIDE { + return got_load_state_change_done_ && got_load_end_; + } + + virtual bool OnLoadingStateChange(CefRefPtr browser, + bool isLoading) OVERRIDE { + if (!isLoading) + got_load_state_change_done_.yes(); + V_DECLARE(); + // A frame should always exist in the renderer process. + if (isLoading) { + std::string expected_url; + if (nav() > 0) + expected_url = GetPreviousMainURL(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + expected_url)); + } else { + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, true, + GetMainURL())); + } + V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading)); + V_RETURN(); + } + + virtual bool OnLoadStart(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + GetMainURL())); + V_EXPECT_TRUE(parent::OnLoadStart(browser, frame)); + V_RETURN(); + } + + virtual bool OnLoadEnd(CefRefPtr browser, + CefRefPtr frame) OVERRIDE { + got_load_end_.yes(); + V_DECLARE(); + V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true, + GetMainURL())); + V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame)); + V_RETURN(); + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(got_load_state_change_done_); + V_EXPECT_TRUE(got_load_end_); + V_EXPECT_TRUE(parent::Finalize()); + V_RETURN(); + } + + private: + // Helpers for calling VerifySingleBrowserFrames. + std::string GetMainURL() const { + return GetMultiNavURL(kFrameNavOrigin0, nav()); + } + std::string GetPreviousMainURL() { + EXPECT_GT(nav(), 0); + return GetMultiNavURL(kFrameNavOrigin0, nav() - 1); + } + + TrackCallback got_load_state_change_done_; + TrackCallback got_load_end_; +}; + +class FrameNavExpectationsFactoryBrowserTestMultiNav : + public FrameNavExpectationsFactoryBrowser { + public: + FrameNavExpectationsFactoryBrowserTestMultiNav() + : nav_count_(0) {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_MULTI_NAV; + } + + virtual bool HasMoreNavigations() const OVERRIDE { + return (nav_count_ < kMaxMultiNavNavigations); + } + + virtual bool Finalize() OVERRIDE { + V_DECLARE(); + V_EXPECT_TRUE(nav_count_ == kMaxMultiNavNavigations); + V_RETURN(); + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + nav_count_++; + return make_scoped_ptr( + new FrameNavExpectationsBrowserTestMultiNav(nav)); + } + + private: + int nav_count_; +}; + +class FrameNavExpectationsFactoryRendererTestMultiNav : + public FrameNavExpectationsFactoryRenderer { + public: + FrameNavExpectationsFactoryRendererTestMultiNav() {} + + virtual FrameNavFactoryId GetID() const OVERRIDE { + return FNF_ID_MULTI_NAV; + } + + protected: + virtual scoped_ptr Create(int nav) OVERRIDE { + return make_scoped_ptr( + new FrameNavExpectationsRendererTestMultiNav(nav)); + } +}; + +} // namespace + +// Test that multiple navigation works. +FRAME_TEST(MultiNav, FNF_ID_MULTI_NAV) + + +namespace { + +const char kFrame0Name[] = ""; +const char kFrame1Name[] = "nav2"; +const char kFrame2Name[] = "-->"; + +bool VerifyBrowserIframe(CefRefPtr browser, + CefRefPtr frame, + const std::string& origin, + int frame_number) { + V_DECLARE(); + + // frame0 contains frame1 contains frame2. + CefRefPtr frame0, frame1, frame2; + CefRefPtr frame0b, frame1b, frame2b; + int64 frame0id, frame1id, frame2id; + std::string frame0url, frame1url, frame2url; + + // Find frames by name. + frame0 = browser->GetFrame(kFrame0Name); + V_EXPECT_TRUE(frame0); + frame1 = browser->GetFrame(kFrame1Name); + V_EXPECT_TRUE(frame1); + frame2 = browser->GetFrame(kFrame2Name); + V_EXPECT_TRUE(frame2); + + // Verify that the name matches. + V_EXPECT_TRUE(frame0->GetName().ToString() == kFrame0Name); + V_EXPECT_TRUE(frame1->GetName().ToString() == kFrame1Name); + V_EXPECT_TRUE(frame2->GetName().ToString() == kFrame2Name); + + // Verify that the URL matches. + frame0url = GetMultiNavURL(origin, 0); + V_EXPECT_TRUE(frame0->GetURL() == frame0url); + frame1url = GetMultiNavURL(origin, 1); + V_EXPECT_TRUE(frame1->GetURL() == frame1url); + frame2url = GetMultiNavURL(origin, 2); + V_EXPECT_TRUE(frame2->GetURL() == frame2url); + + // Verify that the frame id is valid. + frame0id = frame0->GetIdentifier(); + V_EXPECT_TRUE(frame0id > 0); + frame1id = frame1->GetIdentifier(); + V_EXPECT_TRUE(frame1id > 0); + frame2id = frame2->GetIdentifier(); + V_EXPECT_TRUE(frame2id > 0); + + // Verify that the current frame has the correct id. + if (frame_number == 0) { + V_EXPECT_TRUE(frame->GetIdentifier() == frame0id); + } else if (frame_number == 1) { + V_EXPECT_TRUE(frame->GetIdentifier() == frame1id); + } else if (frame_number == 2) { + V_EXPECT_TRUE(frame->GetIdentifier() == frame2id); + } + + // Find frames by id. + frame0b = browser->GetFrame(frame0->GetIdentifier()); + V_EXPECT_TRUE(frame0b); + frame1b = browser->GetFrame(frame1->GetIdentifier()); + V_EXPECT_TRUE(frame1b); + frame2b = browser->GetFrame(frame2->GetIdentifier()); + V_EXPECT_TRUE(frame2b); + + // Verify that the id matches. + V_EXPECT_TRUE(frame0b->GetIdentifier() == frame0id); + V_EXPECT_TRUE(frame1b->GetIdentifier() == frame1id); + V_EXPECT_TRUE(frame2b->GetIdentifier() == frame2id); + + V_EXPECT_TRUE(browser->GetFrameCount() == 3U); + + // Verify the GetFrameNames result. + std::vector names; + browser->GetFrameNames(names); + V_EXPECT_TRUE(names.size() == 3U); + V_EXPECT_TRUE(names[0].ToString() == kFrame0Name); + V_EXPECT_TRUE(names[1].ToString() == kFrame1Name); + V_EXPECT_TRUE(names[2].ToString() == kFrame2Name); + + // Verify the GetFrameIdentifiers result. + std::vector idents; + browser->GetFrameIdentifiers(idents); + V_EXPECT_TRUE(idents.size() == 3U); + V_EXPECT_TRUE(idents[0] == frame0->GetIdentifier()); + V_EXPECT_TRUE(idents[1] == frame1->GetIdentifier()); + V_EXPECT_TRUE(idents[2] == frame2->GetIdentifier()); + + // Verify parent hierarchy. + V_EXPECT_TRUE(frame0->GetParent() == NULL); + V_EXPECT_TRUE(frame1->GetParent()->GetIdentifier() == frame0id); + V_EXPECT_TRUE(frame2->GetParent()->GetIdentifier() == frame1id); + + V_RETURN(); +} + +// Test that nested iframes work. +class FrameNavExpectationsBrowserTestNestedIframes : + public FrameNavExpectationsBrowserMultiNav { + public: + typedef FrameNavExpectationsBrowserMultiNav parent; + + FrameNavExpectationsBrowserTestNestedIframes(int nav, bool same_origin) + : parent(nav), + same_origin_(same_origin) { + // In the browser process we can rely on the |nav| value to determine the + // origin. + if (same_origin) { + origin_ = kFrameNavOrigin0; + } else switch (nav) { + case 0: + origin_ = kFrameNavOrigin0; + break; + case 1: + origin_ = kFrameNavOrigin1; + break; + case 2: + origin_ = kFrameNavOrigin2; + break; + default: + EXPECT_TRUE(false); // Not reached. + break; + } + } + + virtual std::string GetMainURL() OVERRIDE { + // Load the first (main) frame. + return GetMultiNavURL(origin_, 0); + } + + virtual std::string GetContentForURL(const std::string& url) OVERRIDE { + const int frame_number = GetNavFromMultiNavURL(url); + switch (frame_number) { + case 0: + // Frame 0. Contains a named iframe. + return "Nav1