]> git.sesse.net Git - casparcg/blobdiff - dependencies64/cef/linux/tests/ceftests/frame_unittest.cc
Upgrade CEF to 3.3029.1611.g44e39a8 / Chromium 58.0.3029.81.
[casparcg] / dependencies64 / cef / linux / tests / ceftests / frame_unittest.cc
diff --git a/dependencies64/cef/linux/tests/ceftests/frame_unittest.cc b/dependencies64/cef/linux/tests/ceftests/frame_unittest.cc
new file mode 100644 (file)
index 0000000..faa5e68
--- /dev/null
@@ -0,0 +1,2350 @@
+// 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 "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 "tests/ceftests/test_handler.h"
+#include "tests/gtest/include/gtest/gtest.h"
+#include "tests/shared/browser/client_app_browser.h"
+#include "tests/shared/renderer/client_app_renderer.h"
+
+using client::ClientAppBrowser;
+using client::ClientAppRenderer;
+
+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<void(CefRefPtr<CefBrowser>)> CompletionCallback;
+
+  FrameNavExpectations(int nav, bool renderer)
+    : nav_(nav),
+      renderer_(renderer) {
+  }
+  virtual ~FrameNavExpectations() {}
+
+  // Browser and renderer notifications.
+  virtual bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                                    bool isLoading) { return true; }
+  virtual bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                           CefRefPtr<CefFrame> frame) { return true; }
+  virtual bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                         CefRefPtr<CefFrame> 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<CefBrowser> 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<CefBrowser> browser) {
+    EXPECT_TRUE(browser.get());
+    return true;
+  }
+  virtual bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                              CefRefPtr<CefFrame> frame,
+                              const std::string& url) {
+    EXPECT_TRUE(browser.get());
+    EXPECT_TRUE(frame.get());
+    EXPECT_FALSE(url.empty());
+    return true;
+  }
+  virtual bool GetResourceHandler(CefRefPtr<CefBrowser> browser,
+                                  CefRefPtr<CefFrame> frame) {
+    EXPECT_TRUE(browser.get());
+    EXPECT_TRUE(frame.get());
+    return true;
+  }
+
+  // Called when the renderer signals completion.
+  virtual bool OnRendererComplete(CefRefPtr<CefBrowser> 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<CefBrowser> browser,
+                                  CefRefPtr<CefFrame> 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<FrameNavExpectationsFactoryBrowser>
+      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<FrameNavExpectationsBrowser> Create(
+      int nav,
+      const FrameNavExpectations::CompletionCallback& completion_callback) {
+    scoped_ptr<FrameNavExpectationsBrowser> expectations;
+    expectations = Create(nav);
+    expectations->set_completion_callback(completion_callback);
+    return expectations.Pass();
+  }
+
+ protected:
+  // Implement in the test-specific factory instance.
+  virtual scoped_ptr<FrameNavExpectationsBrowser> 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<FrameNavExpectationsFactoryRenderer>
+      FromID(FrameNavFactoryId id);
+
+  scoped_ptr<FrameNavExpectationsRenderer> Create(
+      int nav,
+      const FrameNavExpectations::CompletionCallback& completion_callback) {
+    scoped_ptr<FrameNavExpectationsRenderer> expectations;
+    expectations = Create(nav);
+    expectations->set_completion_callback(completion_callback);
+    return expectations.Pass();
+  }
+
+ protected:
+  // Implement in the test-specific factory instance.
+  virtual scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) =0;
+};
+
+
+// Browser side app delegate.
+class FrameNavBrowserTest : public ClientAppBrowser::Delegate {
+ public:
+  FrameNavBrowserTest() {}
+
+  void OnBeforeChildProcessLaunch(
+      CefRefPtr<ClientAppBrowser> app,
+      CefRefPtr<CefCommandLine> 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 ClientAppRenderer::Delegate,
+                             public CefLoadHandler {
+ public:
+  FrameNavRendererTest()
+      : run_test_(false),
+        nav_(0) {}
+
+  void OnRenderThreadCreated(
+      CefRefPtr<ClientAppRenderer> app,
+      CefRefPtr<CefListValue> 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<CefCommandLine> 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<CefCommandLine> command_line =
+          CefCommandLine::GetGlobalCommandLine();
+      if (command_line->HasSwitch(kTestFactoryIdArg)) {
+        factory_id = static_cast<FrameNavFactoryId>(
+            atoi(command_line->GetSwitchValue(
+                kTestFactoryIdArg).ToString().c_str()));
+        if (factory_id == FNF_ID_INVALID)
+          return;
+      }
+    }
+
+    run_test_ = true;
+    factory_ = FrameNavExpectationsFactoryRenderer::FromID(factory_id);
+  }
+
+  CefRefPtr<CefLoadHandler> GetLoadHandler(
+      CefRefPtr<ClientAppRenderer> app) override {
+    if (!run_test_)
+      return NULL;
+
+    return this;
+  }
+
+ void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                           bool isLoading,
+                           bool canGoBack,
+                           bool canGoForward) override {
+    CreateExpectationsIfNecessary();
+    EXPECT_TRUE(expectations_->OnLoadingStateChange(browser, isLoading)) <<
+                "isLoading = " << isLoading << ", nav = " << nav_;
+  }
+
+  void OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame,
+                   TransitionType transition_type) override {
+    CreateExpectationsIfNecessary();
+    EXPECT_TRUE(expectations_->OnLoadStart(browser, frame)) << "nav = " << nav_;
+  }
+
+  void OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame,
+                 int httpStatusCode) override {
+    CreateExpectationsIfNecessary();
+    EXPECT_TRUE(expectations_->OnLoadEnd(browser, frame)) << "nav = " << nav_;
+  }
+
+  bool OnBeforeNavigation(CefRefPtr<ClientAppRenderer> app,
+                          CefRefPtr<CefBrowser> browser,
+                          CefRefPtr<CefFrame> frame,
+                          CefRefPtr<CefRequest> 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<CefBrowser> 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<CefProcessMessage> return_msg =
+        CefProcessMessage::Create(kFrameNavMsg);
+    CefRefPtr<CefListValue> 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<FrameNavExpectationsFactoryRenderer> factory_;
+  scoped_ptr<FrameNavExpectationsRenderer> expectations_;
+
+  IMPLEMENT_REFCOUNTING(FrameNavRendererTest);
+};
+
+// Browser side handler.
+class FrameNavTestHandler : public TestHandler {
+ public:
+  explicit FrameNavTestHandler(FrameNavFactoryId factory_id)
+      : nav_(0),
+        factory_(FrameNavExpectationsFactoryBrowser::FromID(factory_id)) {
+    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;
+  }
+
+  ~FrameNavTestHandler() override {
+    EXPECT_TRUE(got_destroyed_);
+    g_frame_nav_test = false;
+    g_frame_nav_factory_id = FNF_ID_INVALID;
+  }
+
+  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());
+
+    // Time out the test after a reasonable period of time.
+    SetTestTimeout();
+  }
+
+  // Transition to the next navigation.
+  // Will be called via FrameNavExpectations::SignalComplete.
+  void RunNextNav(CefRefPtr<CefBrowser> 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());
+  }
+
+  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
+    TestHandler::OnAfterCreated(browser);
+
+    EXPECT_TRUE(expectations_->OnAfterCreated(browser)) << "nav = " << nav_;
+  }
+
+  CefRefPtr<CefResourceHandler> GetResourceHandler(
+      CefRefPtr<CefBrowser> browser,
+      CefRefPtr<CefFrame> frame,
+      CefRefPtr<CefRequest> 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<CefStreamReader> stream =
+          CefStreamReader::CreateForData(
+              static_cast<void*>(const_cast<char*>(content.c_str())),
+              content.length());
+      return new CefStreamResourceHandler("text/html", stream);
+  }
+
+  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                      CefRefPtr<CefFrame> frame,
+                      CefRefPtr<CefRequest> request,
+                      bool is_redirect) override {
+    EXPECT_TRUE(
+        expectations_->OnBeforeBrowse(browser, frame, request->GetURL())) <<
+        "nav = " << nav_;
+
+    return false;
+  }
+
+  void OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading,
+                            bool canGoBack,
+                            bool canGoForward) override {
+    EXPECT_TRUE(expectations_->OnLoadingStateChange(browser, isLoading)) <<
+                "isLoading = " << isLoading << ", nav = " << nav_;;
+  }
+
+  void OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame,
+                   TransitionType transition_type) override {
+    EXPECT_TRUE(expectations_->OnLoadStart(browser, frame)) << "nav = " << nav_;
+  }
+
+  void OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame,
+                 int httpStatusCode) override {
+    EXPECT_TRUE(expectations_->OnLoadEnd(browser, frame)) << "nav = " << nav_;
+  }
+
+  bool OnProcessMessageReceived(
+      CefRefPtr<CefBrowser> browser,
+      CefProcessId source_process,
+      CefRefPtr<CefProcessMessage> message) override {
+    if (message->GetName().ToString() == kFrameNavMsg) {
+      // Test that the renderer side succeeded.
+      CefRefPtr<CefListValue> 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;
+  }
+
+  void DestroyTest() override {
+    if (got_destroyed_)
+      return;
+
+    got_destroyed_.yes();
+
+    // 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<FrameNavExpectationsFactoryBrowser> factory_;
+  scoped_ptr<FrameNavExpectationsBrowser> expectations_;
+
+  IMPLEMENT_REFCOUNTING(FrameNavTestHandler);
+};
+
+// Helper for defining frame tests.
+#define FRAME_TEST(name, factory_id) \
+    TEST(FrameTest, name) { \
+      CefRefPtr<FrameNavTestHandler> handler = \
+          new FrameNavTestHandler(factory_id); \
+      handler->ExecuteTest(); \
+      ReleaseAndWaitForDestructor(handler); \
+    }
+
+
+// Browser process expectations for a single navigation.
+class FrameNavExpectationsBrowserSingleNav :
+    public FrameNavExpectationsBrowser {
+ public:
+  explicit FrameNavExpectationsBrowserSingleNav(int nav)
+    : FrameNavExpectationsBrowser(nav) {
+  }
+
+  ~FrameNavExpectationsBrowserSingleNav() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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;
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_start_);
+    got_load_start_.yes();
+    return true;
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_end_);
+    got_load_end_.yes();
+    SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
+    EXPECT_FALSE(got_after_created_);
+    got_after_created_.yes();
+    return true;
+  }
+
+  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                      CefRefPtr<CefFrame> frame,
+                      const std::string& url) override {
+    EXPECT_FALSE(got_before_browse_);
+    got_before_browse_.yes();
+    return true;
+  }
+
+  bool GetResourceHandler(CefRefPtr<CefBrowser> browser,
+                          CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_get_resource_handler_);
+    got_get_resource_handler_.yes();
+    return true;
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> 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;
+  }
+
+  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<CefBrowser> 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) {
+  }
+
+  ~FrameNavExpectationsRendererSingleNav() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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;
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_start_);
+    got_load_start_.yes();
+    return true;
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_end_);
+    got_load_end_.yes();
+    SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool OnBeforeNavigation(CefRefPtr<CefBrowser> browser,
+                          CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_before_navigation_);
+    got_before_navigation_.yes();
+    return true;
+  }
+
+  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<CefBrowser> 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) {
+  }
+
+  ~FrameNavExpectationsBrowserTestSingleNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  std::string GetMainURL() override {
+    EXPECT_FALSE(got_get_main_url_);
+    got_get_main_url_.yes();
+    return kFrameNavOrigin0;
+  }
+
+  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 "<html><body>Nav</body></html>";
+  }
+
+  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) {
+  }
+
+  ~FrameNavExpectationsRendererTestSingleNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  bool Finalize() override {
+    EXPECT_FALSE(got_finalize_);
+    got_finalize_.yes();
+    return parent::Finalize();
+  }
+
+ private:
+  TrackCallback got_finalize_;
+};
+
+class FrameNavExpectationsFactoryBrowserTestSingleNavHarness :
+    public FrameNavExpectationsFactoryBrowser {
+ public:
+  FrameNavExpectationsFactoryBrowserTestSingleNavHarness() {}
+
+  ~FrameNavExpectationsFactoryBrowserTestSingleNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_SINGLE_NAV_HARNESS;
+  }
+
+  bool HasMoreNavigations() const override {
+    EXPECT_FALSE(got_get_browser_navigation_count_);
+    got_get_browser_navigation_count_.yes();
+    return false;
+  }
+
+  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:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    EXPECT_FALSE(got_create_);
+    got_create_.yes();
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestSingleNavHarness(nav));
+  }
+
+ private:
+  mutable TrackCallback got_get_browser_navigation_count_;
+  TrackCallback got_create_;
+  TrackCallback got_finalize_;
+};
+
+class FrameNavExpectationsFactoryRendererTestSingleNavHarness :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestSingleNavHarness() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_SINGLE_NAV_HARNESS;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        new FrameNavExpectationsRendererTestSingleNavHarness(nav));
+  }
+};
+
+}  // namespace
+
+// Test that the single nav harness works.
+FRAME_TEST(SingleNavHarness, FNF_ID_SINGLE_NAV_HARNESS)
+
+
+namespace {
+
+bool VerifySingleBrowserFrame(CefRefPtr<CefBrowser> browser,
+                              CefRefPtr<CefFrame> frame,
+                              bool frame_should_exist,
+                              const std::string& expected_url) {
+  V_DECLARE();
+  V_EXPECT_TRUE(frame.get());
+  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().get());
+  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<CefBrowser> browser,
+                               CefRefPtr<CefFrame> frame,
+                               bool frame_should_exist,
+                               const std::string& expected_url) {
+  V_DECLARE();
+  V_EXPECT_TRUE(browser.get());
+
+  // |frame| may be NULL for callbacks that don't specify one.
+  if (frame.get()) {
+    V_EXPECT_TRUE(VerifySingleBrowserFrame(browser, frame,
+                                           frame_should_exist, expected_url));
+  }
+
+  CefRefPtr<CefFrame> main_frame = browser->GetMainFrame();
+  V_EXPECT_TRUE(VerifySingleBrowserFrame(browser, main_frame,
+                                         frame_should_exist, expected_url));
+
+  CefRefPtr<CefFrame> 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<int64> 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<CefString> 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) {
+  }
+
+  std::string GetMainURL() override {
+    return kFrameNavOrigin0;
+  }
+
+  std::string GetContentForURL(const std::string& url) override {
+    return "<html><body>Nav</body></html>";
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            kFrameNavOrigin0));
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            kFrameNavOrigin0));
+    V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false,
+                                            std::string()));
+    V_EXPECT_TRUE(parent::OnAfterCreated(browser));
+    V_RETURN();
+  }
+
+  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                      CefRefPtr<CefFrame> frame,
+                      const std::string& url) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            std::string()));
+    V_EXPECT_TRUE(parent::OnBeforeBrowse(browser, frame, url));
+    V_RETURN();
+  }
+
+  bool GetResourceHandler(CefRefPtr<CefBrowser> browser,
+                          CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            std::string()));
+    V_EXPECT_TRUE(parent::GetResourceHandler(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> browser,
+                          int renderer_nav,
+                          bool renderer_result) override {
+    return parent::OnRendererComplete(browser, renderer_nav, renderer_result);
+  }
+
+  bool Finalize() override {
+    return parent::Finalize();
+  }
+};
+
+class FrameNavExpectationsRendererTestSingleNav :
+    public FrameNavExpectationsRendererSingleNav {
+ public:
+  typedef FrameNavExpectationsRendererSingleNav parent;
+
+  explicit FrameNavExpectationsRendererTestSingleNav(int nav)
+    : parent(nav) {
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            kFrameNavOrigin0));
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            kFrameNavOrigin0));
+    V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame));
+    V_RETURN();
+  }
+
+  bool Finalize() override {
+    return parent::Finalize();
+  }
+};
+
+class FrameNavExpectationsFactoryBrowserTestSingleNav :
+    public FrameNavExpectationsFactoryBrowser {
+ public:
+  FrameNavExpectationsFactoryBrowserTestSingleNav() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_SINGLE_NAV;
+  }
+
+  bool HasMoreNavigations() const override {
+    return false;
+  }
+
+  bool Finalize() override {
+    return true;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestSingleNav(nav));
+  }
+};
+
+class FrameNavExpectationsFactoryRendererTestSingleNav :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestSingleNav() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_SINGLE_NAV;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        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) {
+  }
+
+  ~FrameNavExpectationsBrowserMultiNav() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  // Returns true if all navigation is done.
+  virtual bool IsNavigationDone() const =0;
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    if (!isLoading)
+      SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> browser,
+                          int renderer_nav,
+                          bool renderer_result) override {
+    EXPECT_TRUE(renderer_result);
+    SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_FALSE(got_finalize_);
+
+    got_finalize_.yes();
+
+    V_RETURN();
+  }
+
+ private:
+  void SignalCompleteIfDone(CefRefPtr<CefBrowser> 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) {
+  }
+
+  ~FrameNavExpectationsRendererMultiNav() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  // Returns true if all navigation is done.
+  virtual bool IsNavigationDone() const =0;
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    if (!isLoading)
+      SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    SignalCompleteIfDone(browser);
+    return true;
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_FALSE(got_finalize_);
+
+    got_finalize_.yes();
+
+    V_RETURN();
+  }
+
+ private:
+  void SignalCompleteIfDone(CefRefPtr<CefBrowser> 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 size_t start = url.find("/nav");
+  const size_t end = url.find(".html", start);
+  EXPECT_TRUE(start < end && start > 0U);
+  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 size_t pos = url.rfind("/");
+  EXPECT_TRUE(pos > 0U);
+  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) {
+  }
+
+  ~FrameNavExpectationsBrowserTestMultiNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  std::string GetMainURL() override {
+    EXPECT_FALSE(got_get_main_url_);
+    got_get_main_url_.yes();
+    return GetMultiNavURL(kFrameNavOrigin0, nav());
+  }
+
+  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 "<html><body>Nav</body></html>";
+  }
+
+  bool IsNavigationDone() const override {
+    navigation_done_count_++;
+    return got_load_state_change_done_ && got_load_end_ &&
+           got_renderer_complete_;
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    if (!isLoading) {
+      EXPECT_FALSE(got_load_state_change_done_);
+      got_load_state_change_done_.yes();
+    }
+    return parent::OnLoadingStateChange(browser, isLoading);
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_end_);
+    got_load_end_.yes();
+    return parent::OnLoadEnd(browser, frame);
+  }
+
+  bool OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
+    EXPECT_FALSE(got_on_after_created_);
+    got_on_after_created_.yes();
+    return parent::OnAfterCreated(browser);
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> 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);
+  }
+
+  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) {
+  }
+
+  ~FrameNavExpectationsRendererTestMultiNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  bool IsNavigationDone() const override {
+    navigation_done_count_++;
+    return got_load_state_change_done_ && got_load_end_;
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    if (!isLoading) {
+      EXPECT_FALSE(got_load_state_change_done_);
+      got_load_state_change_done_.yes();
+    }
+    return parent::OnLoadingStateChange(browser, isLoading);
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    EXPECT_FALSE(got_load_end_);
+    got_load_end_.yes();
+    return parent::OnLoadEnd(browser, frame);
+  }
+
+  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) {}
+
+  ~FrameNavExpectationsFactoryBrowserTestMultiNavHarness() override {
+    EXPECT_TRUE(got_finalize_);
+  }
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_MULTI_NAV_HARNESS;
+  }
+
+  bool HasMoreNavigations() const override {
+    get_browser_navigation_count_++;
+    return (get_browser_navigation_count_ < kMaxMultiNavNavigations);
+  }
+
+  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:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    create_count_++;
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestMultiNavHarness(nav));
+  }
+
+ private:
+  mutable int get_browser_navigation_count_;
+  int create_count_;
+  TrackCallback got_finalize_;
+};
+
+class FrameNavExpectationsFactoryRendererTestMultiNavHarness :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestMultiNavHarness() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_MULTI_NAV_HARNESS;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        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) {
+  }
+
+  std::string GetMainURL() override {
+    return GetMultiNavURL(kFrameNavOrigin0, nav());
+  }
+
+  std::string GetContentForURL(const std::string& url) override {
+    return "<html><body>Nav</body></html>";
+  }
+
+  bool IsNavigationDone() const override {
+    return got_load_state_change_done_ && got_load_end_ &&
+           got_renderer_complete_;
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            GetMainURL()));
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> 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();
+  }
+
+  bool OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, NULL, false,
+                                            std::string()));
+    V_EXPECT_TRUE(parent::OnAfterCreated(browser));
+    V_RETURN();
+  }
+
+  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                      CefRefPtr<CefFrame> frame,
+                      const std::string& url) 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, url));
+    V_RETURN();
+  }
+
+  bool GetResourceHandler(CefRefPtr<CefBrowser> browser,
+                          CefRefPtr<CefFrame> 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();
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> 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();
+  }
+
+  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) {
+  }
+
+  bool IsNavigationDone() const override {
+    return got_load_state_change_done_ && got_load_end_;
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> 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();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(VerifySingleBrowserFrames(browser, frame, true,
+                                            GetMainURL()));
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> 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();
+  }
+
+  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) {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_MULTI_NAV;
+  }
+
+  bool HasMoreNavigations() const override {
+    return (nav_count_ < kMaxMultiNavNavigations);
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_TRUE(nav_count_ == kMaxMultiNavNavigations);
+    V_RETURN();
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    nav_count_++;
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestMultiNav(nav));
+  }
+
+ private:
+  int nav_count_;
+};
+
+class FrameNavExpectationsFactoryRendererTestMultiNav :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestMultiNav() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_MULTI_NAV;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        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[] = "<!--framePath //nav2/<!--frame0-->-->";
+
+bool VerifyBrowserIframe(CefRefPtr<CefBrowser> browser,
+                         CefRefPtr<CefFrame> frame,
+                         const std::string& origin,
+                         int frame_number) {
+  V_DECLARE();
+
+  // frame0 contains frame1 contains frame2.
+  CefRefPtr<CefFrame> frame0, frame1, frame2;
+  CefRefPtr<CefFrame> frame0b, frame1b, frame2b;
+  int64 frame0id, frame1id, frame2id;
+  std::string frame0url, frame1url, frame2url;
+
+  // Find frames by name.
+  frame0 = browser->GetFrame(kFrame0Name);
+  V_EXPECT_TRUE(frame0.get());
+  frame1 = browser->GetFrame(kFrame1Name);
+  V_EXPECT_TRUE(frame1.get());
+  frame2 = browser->GetFrame(kFrame2Name);
+  V_EXPECT_TRUE(frame2.get());
+
+  // 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.get());
+  frame1b = browser->GetFrame(frame1->GetIdentifier());
+  V_EXPECT_TRUE(frame1b.get());
+  frame2b = browser->GetFrame(frame2->GetIdentifier());
+  V_EXPECT_TRUE(frame2b.get());
+
+  // Verify that the id matches.
+  V_EXPECT_TRUE(frame0b->GetIdentifier() == frame0id);
+  V_EXPECT_TRUE(frame1b->GetIdentifier() == frame1id);
+  V_EXPECT_TRUE(frame2b->GetIdentifier() == frame2id);
+
+  size_t frame_count = browser->GetFrameCount();
+  V_EXPECT_TRUE(frame_count == 3U) << "actual " << frame_count;
+
+  // Verify the GetFrameNames result.
+  std::vector<CefString> 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<int64> 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_FALSE(frame0->GetParent().get());
+  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;
+    }
+  }
+
+  std::string GetMainURL() override {
+    // Load the first (main) frame.
+    return GetMultiNavURL(origin_, 0);
+  }
+
+  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 "<html><body>Nav1<iframe src=\"" +
+               GetMultiNavURL(origin_, 1) +
+               "\" name=\"nav2\"></body></html>";
+      case 1:
+        // Frame 1. Contains an unnamed iframe.
+        return "<html><body>Nav2<iframe src=\"" +
+               GetMultiNavURL(origin_, 2) +
+               "\"></body></html>";
+      case 2:
+        // Frame 2.
+        return "<html><body>Nav3</body></html>";
+      default:
+        EXPECT_TRUE(false);  // Not reached.
+        return "";
+    }
+  }
+
+  bool IsNavigationDone() const override {
+    return got_load_state_change_done_ && got_renderer_complete_ &&
+           got_load_end_[0] && got_load_end_[1] && got_load_end_[2];
+  }
+
+  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
+                      CefRefPtr<CefFrame> frame,
+                      const std::string& url) override {
+    V_DECLARE();
+    V_EXPECT_TRUE(frame.get());
+    const int frame_number = GetNavFromMultiNavURL(url);
+    if (frame_number == 0) {
+      // Main frame.
+      V_EXPECT_TRUE(frame->IsMain());
+    } else {
+      // Sub frame.
+      V_EXPECT_FALSE(frame->IsMain());
+    }
+    V_EXPECT_TRUE(parent::OnBeforeBrowse(browser, frame, url));
+    V_RETURN();
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    V_DECLARE();
+    V_EXPECT_FALSE(got_load_state_change_done_);
+
+    if (!isLoading) {
+      got_load_state_change_done_.yes();
+    }
+
+    V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading));
+    V_RETURN();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    const int frame_number = GetNavFromMultiNavURL(frame->GetURL());
+
+    V_DECLARE();
+    V_EXPECT_FALSE(got_load_start_[frame_number]);
+    V_EXPECT_FALSE(got_load_end_[frame_number]);
+
+    // Notification should be received for parent frame before child frame.
+    if (frame_number == 0) {
+      V_EXPECT_FALSE(got_load_start_[1]);
+      V_EXPECT_FALSE(got_load_start_[2]);
+    } else if (frame_number == 1) {
+      V_EXPECT_TRUE(got_load_start_[0]);
+      V_EXPECT_FALSE(got_load_start_[2]);
+    } else if (frame_number == 2) {
+      V_EXPECT_TRUE(got_load_start_[0]);
+      V_EXPECT_TRUE(got_load_start_[1]);
+    } else {
+      V_EXPECT_TRUE(false);  // Not reached.
+    }
+
+    got_load_start_[frame_number].yes();
+
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    const int frame_number = GetNavFromMultiNavURL(frame->GetURL());
+
+    V_DECLARE();
+    V_EXPECT_TRUE(got_load_start_[frame_number]);
+    V_EXPECT_FALSE(got_load_end_[frame_number]);
+
+    // Notification should be received for child frame before parent frame.
+    if (frame_number == 0) {
+      V_EXPECT_TRUE(got_load_end_[1]);
+      V_EXPECT_TRUE(got_load_end_[2]);
+    } else if (frame_number == 1) {
+      V_EXPECT_FALSE(got_load_end_[0]);
+      V_EXPECT_TRUE(got_load_end_[2]);
+    } else if (frame_number == 2) {
+      V_EXPECT_FALSE(got_load_end_[0]);
+      V_EXPECT_FALSE(got_load_end_[1]);
+    } else {
+      V_EXPECT_TRUE(false);  // Not reached.
+    }
+
+    V_EXPECT_TRUE(VerifyBrowserIframe(browser, frame, origin_, frame_number)) <<
+        "frame_number = " << frame_number;
+
+    got_load_end_[frame_number].yes();
+
+    V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnRendererComplete(CefRefPtr<CefBrowser> browser,
+                          int renderer_nav,
+                          bool renderer_result) override {
+    V_DECLARE();
+    V_EXPECT_FALSE(got_renderer_complete_);
+    if (same_origin_) {
+      V_EXPECT_TRUE(renderer_nav == nav());
+    } else {
+      // Because each navigation is in a new renderer process.
+      V_EXPECT_TRUE(renderer_nav == 0);
+    }
+
+    got_renderer_complete_.yes();
+    
+    V_EXPECT_TRUE(parent::OnRendererComplete(browser, renderer_nav,
+                                             renderer_result));
+    V_RETURN();
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_TRUE(got_load_state_change_done_);
+    V_EXPECT_TRUE(got_load_start_[0]);
+    V_EXPECT_TRUE(got_load_start_[1]);
+    V_EXPECT_TRUE(got_load_start_[2]);
+    V_EXPECT_TRUE(got_load_end_[0]);
+    V_EXPECT_TRUE(got_load_end_[1]);
+    V_EXPECT_TRUE(got_load_end_[2]);
+    V_EXPECT_TRUE(got_renderer_complete_);
+    V_EXPECT_TRUE(parent::Finalize());
+    V_RETURN();
+  }
+
+ private:
+  bool same_origin_;
+  std::string origin_;
+
+  TrackCallback got_load_state_change_done_;
+  TrackCallback got_load_start_[3];
+  TrackCallback got_load_end_[3];
+  TrackCallback got_renderer_complete_;
+};
+
+class FrameNavExpectationsRendererTestNestedIframes :
+    public FrameNavExpectationsRendererMultiNav {
+ public:
+  typedef FrameNavExpectationsRendererMultiNav parent;
+
+  FrameNavExpectationsRendererTestNestedIframes(int nav, bool same_origin)
+    : parent(nav) {
+    if (same_origin)
+      origin_ = kFrameNavOrigin0;
+  }
+
+  bool IsNavigationDone() const override {
+    return got_load_state_change_done_ &&
+           got_load_end_[0] && got_load_end_[1] && got_load_end_[2];
+  }
+
+  bool OnLoadingStateChange(CefRefPtr<CefBrowser> browser,
+                            bool isLoading) override {
+    V_DECLARE();
+    V_EXPECT_FALSE(got_load_state_change_done_);
+
+    if (!isLoading) {
+      got_load_state_change_done_.yes();
+    }
+
+    V_EXPECT_TRUE(parent::OnLoadingStateChange(browser, isLoading));
+    V_RETURN();
+  }
+
+  bool OnLoadStart(CefRefPtr<CefBrowser> browser,
+                   CefRefPtr<CefFrame> frame) override {
+    if (origin_.empty()) {
+      // When navigating different origins we can't rely on the nav() value
+      // because each navigation creates a new renderer process. Get the origin
+      // by parsing the URL instead.
+      origin_ = GetOriginFromMultiNavURL(browser->GetMainFrame()->GetURL());
+    }
+
+    const int frame_number = GetNavFromMultiNavURL(frame->GetURL());
+
+    V_DECLARE();
+    V_EXPECT_FALSE(got_load_start_[frame_number]);
+    V_EXPECT_FALSE(got_load_end_[frame_number]);
+
+    // Notification should be received for parent frame before child frame.
+    if (frame_number == 0) {
+      V_EXPECT_FALSE(got_load_start_[1]);
+      V_EXPECT_FALSE(got_load_start_[2]);
+    } else if (frame_number == 1) {
+      V_EXPECT_TRUE(got_load_start_[0]);
+      V_EXPECT_FALSE(got_load_start_[2]);
+    } else if (frame_number == 2) {
+      V_EXPECT_TRUE(got_load_start_[0]);
+      V_EXPECT_TRUE(got_load_start_[1]);
+    }
+
+    got_load_start_[frame_number].yes();
+
+    V_EXPECT_TRUE(parent::OnLoadStart(browser, frame));
+    V_RETURN();
+  }
+
+  bool OnLoadEnd(CefRefPtr<CefBrowser> browser,
+                 CefRefPtr<CefFrame> frame) override {
+    const int frame_number = GetNavFromMultiNavURL(frame->GetURL());
+
+    V_DECLARE();
+    V_EXPECT_TRUE(got_load_start_[frame_number]);
+    V_EXPECT_FALSE(got_load_end_[frame_number]);
+
+    // Notification should be received for child frame before parent frame.
+    if (frame_number == 0) {
+      V_EXPECT_TRUE(got_load_end_[1]);
+      V_EXPECT_TRUE(got_load_end_[2]);
+    } else if (frame_number == 1) {
+      V_EXPECT_FALSE(got_load_end_[0]);
+      V_EXPECT_TRUE(got_load_end_[2]);
+    } else if (frame_number == 2) {
+      V_EXPECT_FALSE(got_load_end_[0]);
+      V_EXPECT_FALSE(got_load_end_[1]);
+    }
+
+    V_EXPECT_TRUE(VerifyBrowserIframe(browser, frame, origin_, frame_number)) <<
+        "frame_number = " << frame_number;
+
+    got_load_end_[frame_number].yes();
+
+    V_EXPECT_TRUE(parent::OnLoadEnd(browser, frame));
+    V_RETURN();
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_TRUE(got_load_state_change_done_);
+    V_EXPECT_TRUE(got_load_start_[0]);
+    V_EXPECT_TRUE(got_load_start_[1]);
+    V_EXPECT_TRUE(got_load_start_[2]);
+    V_EXPECT_TRUE(got_load_end_[0]);
+    V_EXPECT_TRUE(got_load_end_[1]);
+    V_EXPECT_TRUE(got_load_end_[2]);
+    V_EXPECT_TRUE(parent::Finalize());
+    V_RETURN();
+  }
+
+ private:
+  std::string origin_;
+
+  TrackCallback got_load_state_change_done_;
+  TrackCallback got_load_start_[3];
+  TrackCallback got_load_end_[3];
+};
+
+class FrameNavExpectationsFactoryBrowserTestNestedIframesSameOrigin :
+    public FrameNavExpectationsFactoryBrowser {
+ public:
+  FrameNavExpectationsFactoryBrowserTestNestedIframesSameOrigin()
+    : create_count_(0) {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_NESTED_IFRAMES_SAME_ORIGIN;
+  }
+
+  bool HasMoreNavigations() const override {
+    return (create_count_ < kMaxMultiNavNavigations);
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_TRUE(create_count_ == kMaxMultiNavNavigations);
+    V_RETURN();
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    create_count_++;
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestNestedIframes(nav, true));
+  }
+
+ private:
+  int create_count_;
+};
+
+class FrameNavExpectationsFactoryRendererTestNestedIframesSameOrigin :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestNestedIframesSameOrigin() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_NESTED_IFRAMES_SAME_ORIGIN;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        new FrameNavExpectationsRendererTestNestedIframes(nav,  true));
+  }
+};
+
+}  // namespace
+
+// Test that nested iframes work.
+FRAME_TEST(NestedIframesSameOrigin, FNF_ID_NESTED_IFRAMES_SAME_ORIGIN)
+
+
+namespace {
+
+class FrameNavExpectationsFactoryBrowserTestNestedIframesDiffOrigin :
+    public FrameNavExpectationsFactoryBrowser {
+ public:
+  FrameNavExpectationsFactoryBrowserTestNestedIframesDiffOrigin()
+    : create_count_(0) {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN;
+  }
+
+  bool HasMoreNavigations() const override {
+    return (create_count_ < kMaxMultiNavNavigations);
+  }
+
+  bool Finalize() override {
+    V_DECLARE();
+    V_EXPECT_TRUE(create_count_ == kMaxMultiNavNavigations);
+    V_RETURN();
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsBrowser> Create(int nav) override {
+    create_count_++;
+    return scoped_ptr<FrameNavExpectationsBrowser>(
+        new FrameNavExpectationsBrowserTestNestedIframes(nav, false));
+  }
+
+ private:
+  int create_count_;
+};
+
+class FrameNavExpectationsFactoryRendererTestNestedIframesDiffOrigin :
+    public FrameNavExpectationsFactoryRenderer {
+ public:
+  FrameNavExpectationsFactoryRendererTestNestedIframesDiffOrigin() {}
+
+  FrameNavFactoryId GetID() const override {
+    return FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN;
+  }
+
+ protected:
+  scoped_ptr<FrameNavExpectationsRenderer> Create(int nav) override {
+    return scoped_ptr<FrameNavExpectationsRenderer>(
+        new FrameNavExpectationsRendererTestNestedIframes(nav, false));
+  }
+};
+
+}  // namespace
+
+// Test that nested iframes work.
+FRAME_TEST(NestedIframesDiffOrigin, FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN)
+
+
+namespace {
+
+// Returns a new factory in the browser or renderer process. All factory types
+// must be listed here.
+
+// static
+scoped_ptr<FrameNavExpectationsFactoryBrowser>
+    FrameNavExpectationsFactoryBrowser::FromID(FrameNavFactoryId id) {
+  scoped_ptr<FrameNavExpectationsFactoryBrowser> factory;
+  switch (id) {
+    case FNF_ID_SINGLE_NAV_HARNESS:
+      factory.reset(new FrameNavExpectationsFactoryBrowserTestSingleNavHarness);
+      break;
+    case FNF_ID_SINGLE_NAV:
+      factory.reset(new FrameNavExpectationsFactoryBrowserTestSingleNav);
+      break;
+    case FNF_ID_MULTI_NAV_HARNESS:
+      factory.reset(new FrameNavExpectationsFactoryBrowserTestMultiNavHarness);
+      break;
+    case FNF_ID_MULTI_NAV:
+      factory.reset(new FrameNavExpectationsFactoryBrowserTestMultiNav);
+      break;
+    case FNF_ID_NESTED_IFRAMES_SAME_ORIGIN:
+      factory.reset(
+          new FrameNavExpectationsFactoryBrowserTestNestedIframesSameOrigin);
+      break;
+    case FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN:
+      factory.reset(
+          new FrameNavExpectationsFactoryBrowserTestNestedIframesDiffOrigin);
+      break;
+    default:
+      break;
+  }
+  EXPECT_TRUE(factory);
+  EXPECT_EQ(id, factory->GetID());
+  return factory.Pass();
+}
+
+// static
+scoped_ptr<FrameNavExpectationsFactoryRenderer>
+    FrameNavExpectationsFactoryRenderer::FromID(FrameNavFactoryId id) {
+  scoped_ptr<FrameNavExpectationsFactoryRenderer> factory;
+  switch (id) {
+    case FNF_ID_SINGLE_NAV_HARNESS:
+      factory.reset(
+          new FrameNavExpectationsFactoryRendererTestSingleNavHarness);
+      break;
+    case FNF_ID_SINGLE_NAV:
+      factory.reset(new FrameNavExpectationsFactoryRendererTestSingleNav);
+      break;
+    case FNF_ID_MULTI_NAV_HARNESS:
+      factory.reset(new FrameNavExpectationsFactoryRendererTestMultiNavHarness);
+      break;
+    case FNF_ID_MULTI_NAV:
+      factory.reset(new FrameNavExpectationsFactoryRendererTestMultiNav);
+      break;
+    case FNF_ID_NESTED_IFRAMES_SAME_ORIGIN:
+      factory.reset(
+          new FrameNavExpectationsFactoryRendererTestNestedIframesSameOrigin);
+      break;
+    case FNF_ID_NESTED_IFRAMES_DIFF_ORIGIN:
+      factory.reset(
+          new FrameNavExpectationsFactoryRendererTestNestedIframesDiffOrigin);
+      break;
+    default:
+      break;
+  }
+  EXPECT_TRUE(factory);
+  EXPECT_EQ(id, factory->GetID());
+  return factory.Pass();
+}
+
+}  // namespace
+
+
+// Entry point for creating frame browser test objects.
+// Called from client_app_delegates.cc.
+void CreateFrameBrowserTests(ClientAppBrowser::DelegateSet& delegates) {
+  delegates.insert(new FrameNavBrowserTest);
+}
+
+// Entry point for creating frame renderer test objects.
+// Called from client_app_delegates.cc.
+void CreateFrameRendererTests(ClientAppRenderer::DelegateSet& delegates) {
+  delegates.insert(new FrameNavRendererTest);
+}