// Copyright (c) 2013 The Chromium Embedded Framework Authors. All rights
// reserved. Use of this source code is governed by a BSD-style license that
// can be found in the LICENSE file.

#include "include/base/cef_callback.h"
#include "include/base/cef_weak_ptr.h"
#include "include/cef_request_context.h"
#include "include/cef_request_context_handler.h"
#include "include/wrapper/cef_closure_task.h"
#include "include/wrapper/cef_scoped_temp_dir.h"
#include "tests/ceftests/test_handler.h"
#include "tests/ceftests/test_util.h"
#include "tests/gtest/include/gtest/gtest.h"

TEST(RequestContextTest, BasicGetGlobal) {
  CefRefPtr<CefRequestContext> context1 = CefRequestContext::GetGlobalContext();
  EXPECT_TRUE(context1.get());
  EXPECT_TRUE(context1->IsGlobal());
  EXPECT_TRUE(context1->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context1));

  CefRefPtr<CefRequestContext> context2 = CefRequestContext::GetGlobalContext();
  EXPECT_TRUE(context2.get());
  EXPECT_TRUE(context2->IsGlobal());
  EXPECT_TRUE(context2->IsSame(context2));
  EXPECT_TRUE(context2->IsSharingWith(context2));

  EXPECT_TRUE(context1->IsSame(context2));
  EXPECT_TRUE(context2->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context2));
  EXPECT_TRUE(context2->IsSharingWith(context1));
}

TEST(RequestContextTest, BasicCreate) {
  class Handler : public CefRequestContextHandler {
   public:
    Handler() = default;

   private:
    IMPLEMENT_REFCOUNTING(Handler);
  };

  CefRefPtr<CefRequestContextHandler> handler = new Handler();

  CefRequestContextSettings settings;

  CefRefPtr<CefRequestContext> context1 =
      CefRequestContext::CreateContext(settings, handler.get());
  EXPECT_TRUE(context1.get());
  EXPECT_FALSE(context1->IsGlobal());
  EXPECT_TRUE(context1->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context1));
  EXPECT_EQ(context1->GetHandler().get(), handler.get());

  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(settings, handler.get());
  EXPECT_TRUE(context2.get());
  EXPECT_FALSE(context2->IsGlobal());
  EXPECT_TRUE(context2->IsSame(context2));
  EXPECT_TRUE(context2->IsSharingWith(context2));
  EXPECT_EQ(context2->GetHandler().get(), handler.get());

  EXPECT_FALSE(context1->IsSame(context2));
  EXPECT_FALSE(context1->IsSharingWith(context2));
  EXPECT_FALSE(context2->IsSame(context1));
  EXPECT_FALSE(context2->IsSharingWith(context1));

  CefRefPtr<CefRequestContext> context3 = CefRequestContext::GetGlobalContext();
  EXPECT_TRUE(context3.get());
  EXPECT_FALSE(context3->IsSame(context1));
  EXPECT_FALSE(context3->IsSharingWith(context1));
  EXPECT_FALSE(context3->IsSame(context2));
  EXPECT_FALSE(context3->IsSharingWith(context2));
  EXPECT_FALSE(context1->IsSame(context3));
  EXPECT_FALSE(context1->IsSharingWith(context3));
  EXPECT_FALSE(context2->IsSame(context3));
  EXPECT_FALSE(context2->IsSharingWith(context3));
}

TEST(RequestContextTest, BasicCreateNoHandler) {
  CefRequestContextSettings settings;

  CefRefPtr<CefRequestContext> context1 =
      CefRequestContext::CreateContext(settings, nullptr);
  EXPECT_TRUE(context1.get());
  EXPECT_FALSE(context1->IsGlobal());
  EXPECT_TRUE(context1->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context1));
  EXPECT_FALSE(context1->GetHandler().get());

  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(settings, nullptr);
  EXPECT_TRUE(context2.get());
  EXPECT_FALSE(context2->IsGlobal());
  EXPECT_TRUE(context2->IsSame(context2));
  EXPECT_TRUE(context2->IsSharingWith(context2));
  EXPECT_FALSE(context2->GetHandler().get());

  EXPECT_FALSE(context1->IsSame(context2));
  EXPECT_FALSE(context1->IsSharingWith(context2));
  EXPECT_FALSE(context2->IsSame(context1));
  EXPECT_FALSE(context2->IsSharingWith(context1));

  CefRefPtr<CefRequestContext> context3 = CefRequestContext::GetGlobalContext();
  EXPECT_TRUE(context3.get());
  EXPECT_FALSE(context3->IsSame(context1));
  EXPECT_FALSE(context3->IsSharingWith(context1));
  EXPECT_FALSE(context3->IsSame(context2));
  EXPECT_FALSE(context3->IsSharingWith(context2));
  EXPECT_FALSE(context1->IsSame(context3));
  EXPECT_FALSE(context1->IsSharingWith(context3));
  EXPECT_FALSE(context2->IsSame(context3));
  EXPECT_FALSE(context2->IsSharingWith(context3));
}

TEST(RequestContextTest, BasicCreateSharedGlobal) {
  CefRequestContextSettings settings;

  CefRefPtr<CefRequestContext> context1 = CefRequestContext::GetGlobalContext();
  EXPECT_TRUE(context1.get());
  EXPECT_TRUE(context1->IsGlobal());
  EXPECT_TRUE(context1->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context1));

  // Returns the same global context.
  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(context1, nullptr);
  EXPECT_TRUE(context2.get());
  EXPECT_TRUE(context2->IsGlobal());
  EXPECT_TRUE(context2->IsSame(context2));
  EXPECT_TRUE(context2->IsSame(context1));
  EXPECT_TRUE(context1->IsSame(context2));
  EXPECT_TRUE(context2->IsSharingWith(context2));
  EXPECT_TRUE(context2->IsSharingWith(context1));
  EXPECT_TRUE(context1->IsSharingWith(context2));
}

TEST(RequestContextTest, BasicCreateSharedOnDisk) {
  CefScopedTempDir tempdir;
  EXPECT_TRUE(tempdir.CreateUniqueTempDirUnderPath(
      CefTestSuite::GetInstance()->root_cache_path()));

  CefRequestContextSettings settings;
  CefString(&settings.cache_path) = tempdir.GetPath();

  CefRefPtr<CefRequestContext> context1 =
      CefRequestContext::CreateContext(settings, nullptr);
  EXPECT_TRUE(context1.get());
  EXPECT_FALSE(context1->IsGlobal());
  EXPECT_TRUE(context1->IsSame(context1));
  EXPECT_TRUE(context1->IsSharingWith(context1));

  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(context1, nullptr);
  EXPECT_TRUE(context2.get());
  EXPECT_FALSE(context2->IsGlobal());
  EXPECT_TRUE(context2->IsSame(context2));
  EXPECT_FALSE(context2->IsSame(context1));
  EXPECT_FALSE(context1->IsSame(context2));
  EXPECT_TRUE(context2->IsSharingWith(context2));
  EXPECT_TRUE(context2->IsSharingWith(context1));
  EXPECT_TRUE(context1->IsSharingWith(context2));

  CefRefPtr<CefRequestContext> context3 =
      CefRequestContext::CreateContext(context2, nullptr);
  EXPECT_TRUE(context3.get());
  EXPECT_FALSE(context3->IsGlobal());
  EXPECT_TRUE(context3->IsSame(context3));
  EXPECT_FALSE(context3->IsSame(context2));
  EXPECT_FALSE(context3->IsSame(context1));
  EXPECT_FALSE(context1->IsSame(context3));
  EXPECT_FALSE(context2->IsSame(context3));
  EXPECT_TRUE(context3->IsSharingWith(context3));
  EXPECT_TRUE(context3->IsSharingWith(context2));
  EXPECT_TRUE(context3->IsSharingWith(context1));
  EXPECT_TRUE(context1->IsSharingWith(context3));
  EXPECT_TRUE(context2->IsSharingWith(context3));

  CefRefPtr<CefRequestContext> context4 =
      CefRequestContext::CreateContext(context1, nullptr);
  EXPECT_TRUE(context4.get());
  EXPECT_FALSE(context4->IsGlobal());
  EXPECT_TRUE(context4->IsSame(context4));
  EXPECT_FALSE(context4->IsSame(context3));
  EXPECT_FALSE(context4->IsSame(context2));
  EXPECT_FALSE(context4->IsSame(context1));
  EXPECT_FALSE(context1->IsSame(context4));
  EXPECT_FALSE(context2->IsSame(context4));
  EXPECT_FALSE(context3->IsSame(context4));
  EXPECT_TRUE(context4->IsSharingWith(context4));
  EXPECT_TRUE(context4->IsSharingWith(context3));
  EXPECT_TRUE(context4->IsSharingWith(context2));
  EXPECT_TRUE(context4->IsSharingWith(context1));
  EXPECT_TRUE(context1->IsSharingWith(context4));
  EXPECT_TRUE(context2->IsSharingWith(context4));
  EXPECT_TRUE(context3->IsSharingWith(context4));
}

namespace {

class PopupTestHandler : public TestHandler {
 public:
  enum Mode {
    MODE_WINDOW_OPEN,
    MODE_TARGETED_LINK,
    // The no-referrer popup won't have an opener from the renderer's
    // perspective, but we still track it from the browser's perspective.
    MODE_NOREFERRER_LINK,
  };

  PopupTestHandler(bool same_origin, Mode mode) : mode_(mode) {
    url_ = "https://tests-simple-rch1.com/nav1.html";
    if (same_origin) {
      popup_url_ = "https://tests-simple-rch1.com/pop1.html";
    } else {
      popup_url_ = "https://tests-simple-rch2.com/pop1.html";
    }
  }

  void RunTest() override {
    std::string link;
    if (mode_ == MODE_TARGETED_LINK) {
      link = "<a href=\"" + std::string(popup_url_) +
             "\" target=\"mytarget\"\">CLICK ME</a>";
    } else if (mode_ == MODE_NOREFERRER_LINK) {
      link = "<a href=\"" + std::string(popup_url_) +
             "\" rel=\"noreferrer\" target=\"_blank\"\">CLICK ME</a>";
    }

    AddResource(url_,
                "<html>"
                "<head><script>document.cookie='name1=value1';"
                "function doPopup() { window.open('" +
                    std::string(popup_url_) +
                    "'); }"
                    "</script></head>"
                    "<body><h1>" +
                    link +
                    "</h1></body>"
                    "</html>",
                "text/html");

    AddResource(popup_url_,
                "<html>"
                "<head><script>document.cookie='name2=value2';</script></head>"
                "<body>Nav1</body>"
                "</html>",
                "text/html");

    CefRequestContextSettings settings;

    context_ = CefRequestContext::CreateContext(settings, nullptr);
    cookie_manager_ = context_->GetCookieManager(nullptr);

    GrantPopupPermission(context_, url_);

    // Create browser that loads the 1st URL.
    CreateBrowser(url_, context_);

    // Time out the test after a reasonable period of time.
    SetTestTimeout();
  }

  void OnLoadEnd(CefRefPtr<CefBrowser> browser,
                 CefRefPtr<CefFrame> frame,
                 int httpStatusCode) override {
    CefRefPtr<CefRequestContext> context =
        browser->GetHost()->GetRequestContext();
    EXPECT_TRUE(context.get());
    EXPECT_TRUE(context->IsSame(context_));
    EXPECT_FALSE(context->IsGlobal());

    EXPECT_TRUE(frame->IsMain());

    const std::string& url = frame->GetURL();
    if (url == url_) {
      got_load_end1_.yes();
      LaunchPopup(browser);
    } else if (url == popup_url_) {
      got_load_end2_.yes();
      EXPECT_TRUE(browser->IsPopup());
      // Close the popup window.
      CloseBrowser(browser, true);
    }
  }

  bool OnBeforePopup(CefRefPtr<CefBrowser> browser,
                     CefRefPtr<CefFrame> frame,
                     int popup_id,
                     const CefString& target_url,
                     const CefString& target_frame_name,
                     cef_window_open_disposition_t target_disposition,
                     bool user_gesture,
                     const CefPopupFeatures& popupFeatures,
                     CefWindowInfo& windowInfo,
                     CefRefPtr<CefClient>& client,
                     CefBrowserSettings& settings,
                     CefRefPtr<CefDictionaryValue>& extra_info,
                     bool* no_javascript_access) override {
    EXPECT_FALSE(got_on_before_popup_);
    EXPECT_FALSE(got_on_before_popup_aborted_);
    EXPECT_FALSE(got_on_after_created_popup_);
    got_on_before_popup_.yes();

    // Only ever a single popup with this test.
    EXPECT_EQ(1, popup_id);

    const std::string& url = target_url;
    EXPECT_STREQ(url.c_str(), popup_url_.c_str());

    EXPECT_EQ(CEF_WOD_NEW_FOREGROUND_TAB, target_disposition);

    if (mode_ == MODE_WINDOW_OPEN) {
      EXPECT_FALSE(user_gesture);
    } else {
      EXPECT_TRUE(user_gesture);
    }

    return false;
  }

  void OnBeforePopupAborted(CefRefPtr<CefBrowser> browser,
                            int popup_id) override {
    EXPECT_TRUE(got_on_before_popup_);
    EXPECT_FALSE(got_on_before_popup_aborted_);
    EXPECT_FALSE(got_on_after_created_popup_);
    got_on_before_popup_aborted_.yes();

    // Only ever a single popup with this test.
    EXPECT_EQ(1, popup_id);
  }

  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
    if (browser->IsPopup()) {
      EXPECT_TRUE(got_on_before_popup_);
      EXPECT_FALSE(got_on_before_popup_aborted_);
      got_on_after_created_popup_.yes();
      // Opener is the main browser.
      EXPECT_EQ(GetBrowserId(), browser->GetHost()->GetOpenerIdentifier());
    } else {
      EXPECT_EQ(0, browser->GetHost()->GetOpenerIdentifier());
    }
    TestHandler::OnAfterCreated(browser);
  }

  void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
    TestHandler::OnBeforeClose(browser);

    if (browser->IsPopup()) {
      FinishTest();
    }
  }

 protected:
  void LaunchPopup(CefRefPtr<CefBrowser> browser) {
    if (mode_ == MODE_WINDOW_OPEN) {
      browser->GetMainFrame()->ExecuteJavaScript("doPopup()", url_, 0);
    } else if (mode_ == MODE_TARGETED_LINK || mode_ == MODE_NOREFERRER_LINK) {
      CefMouseEvent mouse_event;
      mouse_event.x = 20;
      mouse_event.y = 20;
      mouse_event.modifiers = 0;
      SendMouseClickEvent(browser, mouse_event);
    } else {
      ADD_FAILURE();  // Not reached.
    }
  }

  void FinishTest() {
    // Verify that the cookies were set correctly.
    class TestVisitor : public CefCookieVisitor {
     public:
      explicit TestVisitor(PopupTestHandler* handler) : handler_(handler) {}
      ~TestVisitor() override {
        // Destroy the test.
        CefPostTask(TID_UI,
                    base::BindOnce(&PopupTestHandler::DestroyTest, handler_));
      }

      bool Visit(const CefCookie& cookie,
                 int count,
                 int total,
                 bool& deleteCookie) override {
        const std::string& name = CefString(&cookie.name);
        const std::string& value = CefString(&cookie.value);
        if (name == "name1" && value == "value1") {
          handler_->got_cookie1_.yes();
          deleteCookie = true;
        } else if (name == "name2" && value == "value2") {
          handler_->got_cookie2_.yes();
          deleteCookie = true;
        }
        return true;
      }

     private:
      PopupTestHandler* handler_;
      IMPLEMENT_REFCOUNTING(TestVisitor);
    };

    cookie_manager_->VisitAllCookies(new TestVisitor(this));
  }

  void DestroyTest() override {
    // Verify test expectations.
    EXPECT_TRUE(got_load_end1_);
    EXPECT_TRUE(got_on_before_popup_);
    EXPECT_FALSE(got_on_before_popup_aborted_);
    EXPECT_TRUE(got_on_after_created_popup_);
    EXPECT_TRUE(got_load_end2_);
    EXPECT_TRUE(got_cookie1_);
    EXPECT_TRUE(got_cookie2_);
    context_ = nullptr;

    TestHandler::DestroyTest();
  }

  std::string url_;
  std::string popup_url_;
  Mode mode_;

  CefRefPtr<CefRequestContext> context_;
  CefRefPtr<CefCookieManager> cookie_manager_;

  TrackCallback got_load_end1_;
  TrackCallback got_on_before_popup_;
  TrackCallback got_on_before_popup_aborted_;
  TrackCallback got_on_after_created_popup_;
  TrackCallback got_load_end2_;
  TrackCallback got_cookie1_;
  TrackCallback got_cookie2_;

  IMPLEMENT_REFCOUNTING(PopupTestHandler);
};

}  // namespace

// Test that a popup created using window.open() will get the same request
// context as the parent browser.
TEST(RequestContextTest, PopupBasicWindowOpenSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true, PopupTestHandler::MODE_WINDOW_OPEN);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

TEST(RequestContextTest, PopupBasicWindowOpenDifferentOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(false, PopupTestHandler::MODE_WINDOW_OPEN);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a popup created using a targeted link will get the same request
// context as the parent browser.
TEST(RequestContextTest, PopupBasicTargetedLinkSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true, PopupTestHandler::MODE_TARGETED_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

TEST(RequestContextTest, PopupBasicTargetedLinkDifferentOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(false, PopupTestHandler::MODE_TARGETED_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a popup created using a noreferrer link will get the same
// request context as the parent browser. A new render process will
// be created for the popup browser.
TEST(RequestContextTest, PopupBasicNoReferrerLinkSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true, PopupTestHandler::MODE_NOREFERRER_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

TEST(RequestContextTest, PopupBasicNoReferrerLinkDifferentOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(false, PopupTestHandler::MODE_NOREFERRER_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

const char kPopupNavPageUrl[] = "https://tests-popup.com/page.html";
const char kPopupNavPopupUrl[] = "https://tests-popup.com/popup.html";
const char kPopupNavPopupUrl2[] = "https://tests-popup2.com/popup.html";
const char kPopupNavPopupName[] = "my_popup";

// Browser side.
class PopupNavTestHandler : public TestHandler {
 public:
  enum TestMode {
    ALLOW_CLOSE_POPUP_FIRST,
    ALLOW_CLOSE_POPUP_LAST,
    DENY,
    NAVIGATE_AFTER_CREATION,
    DESTROY_PARENT_BEFORE_CREATION,
    DESTROY_PARENT_BEFORE_CREATION_FORCE,
    DESTROY_PARENT_DURING_CREATION,
    DESTROY_PARENT_DURING_CREATION_FORCE,
    DESTROY_PARENT_AFTER_CREATION,
    DESTROY_PARENT_AFTER_CREATION_FORCE,
  };

  PopupNavTestHandler(TestMode test_mode,
                      TestRequestContextMode rc_mode,
                      const std::string& rc_cache_path)
      : mode_(test_mode), rc_mode_(rc_mode), rc_cache_path_(rc_cache_path) {}

  void RunTest() override {
    // Add the resources that we will navigate to/from.
    std::string page = "<html><script>function doPopup() { window.open('" +
                       std::string(kPopupNavPopupUrl) + "', '" +
                       std::string(kPopupNavPopupName) +
                       "'); }</script>Page</html>";
    AddResource(kPopupNavPageUrl, page, "text/html");
    AddResource(kPopupNavPopupUrl, "<html>Popup</html>", "text/html");
    if (mode_ == NAVIGATE_AFTER_CREATION) {
      AddResource(kPopupNavPopupUrl2, "<html>Popup2</html>", "text/html");
    }

    // By default, TestHandler signals test completion when all CefBrowsers
    // have closed. For this test we instead want to wait for an explicit call
    // to DestroyTest before signaling test completion.
    SetSignalTestCompletionCount(1U);

    CreateTestRequestContext(
        rc_mode_, rc_cache_path_,
        base::BindOnce(&PopupNavTestHandler::RunTestContinue, this));

    // Time out the test after a reasonable period of time.
    SetTestTimeout();
  }

  void RunTestContinue(CefRefPtr<CefRequestContext> request_context) {
    EXPECT_UI_THREAD();

    if (request_context) {
      GrantPopupPermission(request_context, kPopupNavPageUrl);
    } else {
      GrantPopupPermission(CefRequestContext::GetGlobalContext(),
                           kPopupNavPageUrl);
    }

    // Create the browser.
    CreateBrowser(kPopupNavPageUrl, request_context);
  }

  bool OnBeforePopup(CefRefPtr<CefBrowser> browser,
                     CefRefPtr<CefFrame> frame,
                     int popup_id,
                     const CefString& target_url,
                     const CefString& target_frame_name,
                     cef_window_open_disposition_t target_disposition,
                     bool user_gesture,
                     const CefPopupFeatures& popupFeatures,
                     CefWindowInfo& windowInfo,
                     CefRefPtr<CefClient>& client,
                     CefBrowserSettings& settings,
                     CefRefPtr<CefDictionaryValue>& extra_info,
                     bool* no_javascript_access) override {
    EXPECT_FALSE(got_on_before_popup_);
    EXPECT_FALSE(got_on_before_popup_aborted_);
    got_on_before_popup_.yes();

    EXPECT_TRUE(CefCurrentlyOn(TID_UI));

    // Only ever a single popup with this test.
    EXPECT_EQ(1, popup_id);
    opener_browser_id_ = browser->GetIdentifier();
    EXPECT_EQ(GetBrowserId(), opener_browser_id_);

    EXPECT_STREQ(kPopupNavPageUrl, frame->GetURL().ToString().c_str());
    EXPECT_STREQ(kPopupNavPopupUrl, target_url.ToString().c_str());
    EXPECT_STREQ(kPopupNavPopupName, target_frame_name.ToString().c_str());
    EXPECT_EQ(CEF_WOD_NEW_FOREGROUND_TAB, target_disposition);
    EXPECT_FALSE(user_gesture);
    EXPECT_FALSE(*no_javascript_access);

    if (mode_ == DESTROY_PARENT_DURING_CREATION ||
        mode_ == DESTROY_PARENT_DURING_CREATION_FORCE) {
      // Destroy the main (parent) browser while popup creation is pending.
      CloseBrowser(browser, mode_ == DESTROY_PARENT_DURING_CREATION_FORCE);
    }

    return (mode_ == DENY);  // Return true to cancel the popup.
  }

  void OnBeforePopupAborted(CefRefPtr<CefBrowser> browser,
                            int popup_id) override {
    EXPECT_TRUE(got_on_before_popup_);
    EXPECT_FALSE(got_on_before_popup_aborted_);
    got_on_before_popup_aborted_.yes();

    EXPECT_TRUE(CefCurrentlyOn(TID_UI));

    // Can't use GetBrowserId() here because the opener is already closing.
    EXPECT_EQ(opener_browser_id_, browser->GetIdentifier());
    EXPECT_FALSE(browser->IsValid());

    // Only ever a single popup with this test.
    EXPECT_EQ(1, popup_id);
  }

  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
    TestHandler::OnAfterCreated(browser);
    if (browser->IsPopup()) {
      EXPECT_TRUE(got_on_before_popup_);
      EXPECT_EQ(opener_browser_id_, browser->GetHost()->GetOpenerIdentifier());
    } else {
      EXPECT_FALSE(got_on_before_popup_);
      EXPECT_EQ(0, browser->GetHost()->GetOpenerIdentifier());
    }
    EXPECT_FALSE(got_on_before_popup_aborted_);

    if (browser->IsPopup() && (mode_ == DESTROY_PARENT_AFTER_CREATION ||
                               mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE)) {
      // Destroy the main (parent) browser immediately after the popup is
      // created.
      CloseBrowser(GetBrowser(), mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE);
    }

    if (mode_ == NAVIGATE_AFTER_CREATION && browser->IsPopup()) {
      // Navigate to the 2nd popup URL instead of the 1st popup URL.
      browser->GetMainFrame()->LoadURL(kPopupNavPopupUrl2);
    }
  }

  void OnLoadStart(CefRefPtr<CefBrowser> browser,
                   CefRefPtr<CefFrame> frame,
                   TransitionType transition_type) override {
    const std::string& url = frame->GetURL();
    if (url == kPopupNavPageUrl) {
      EXPECT_FALSE(got_load_start_);
      got_load_start_.yes();
    } else if (url == kPopupNavPopupUrl) {
      EXPECT_FALSE(got_popup_load_start_);
      got_popup_load_start_.yes();
    } else if (url == kPopupNavPopupUrl2) {
      EXPECT_FALSE(got_popup_load_start2_);
      got_popup_load_start2_.yes();
    }
  }

  void OnLoadError(CefRefPtr<CefBrowser> browser,
                   CefRefPtr<CefFrame> frame,
                   ErrorCode errorCode,
                   const CefString& errorText,
                   const CefString& failedUrl) override {
    if (failedUrl == kPopupNavPageUrl) {
      EXPECT_FALSE(got_load_error_);
      got_load_error_.yes();
    } else if (failedUrl == kPopupNavPopupUrl) {
      EXPECT_FALSE(got_popup_load_error_);
      got_popup_load_error_.yes();
    } else if (failedUrl == kPopupNavPopupUrl2) {
      EXPECT_FALSE(got_popup_load_error2_);
      got_popup_load_error2_.yes();
    }
  }

  void OnLoadEnd(CefRefPtr<CefBrowser> browser,
                 CefRefPtr<CefFrame> frame,
                 int httpStatusCode) override {
    const std::string& url = frame->GetURL();
    if (url == kPopupNavPageUrl) {
      EXPECT_FALSE(got_load_end_);
      got_load_end_.yes();

      frame->ExecuteJavaScript("doPopup()", kPopupNavPageUrl, 0);

      if (mode_ == DESTROY_PARENT_BEFORE_CREATION ||
          mode_ == DESTROY_PARENT_BEFORE_CREATION_FORCE) {
        // Destroy the main (parent) browser immediately before the popup is
        // created.
        CloseBrowser(browser, mode_ == DESTROY_PARENT_BEFORE_CREATION_FORCE);
      }

      if (mode_ == DENY) {
        // Wait a bit to make sure the popup window isn't created.
        CefPostDelayedTask(TID_UI,
                           base::BindOnce(&PopupNavTestHandler::DestroyTest,
                                          weak_ptr_factory_.GetWeakPtr()),
                           200);
      }
    } else if (url == kPopupNavPopupUrl) {
      EXPECT_FALSE(got_popup_load_end_);
      got_popup_load_end_.yes();

      if (mode_ == ALLOW_CLOSE_POPUP_FIRST) {
        // Close the popup browser first.
        CloseBrowser(browser, false);
      } else if (mode_ == ALLOW_CLOSE_POPUP_LAST) {
        // Close the main browser first.
        CloseBrowser(GetBrowser(), false);
      } else if (mode_ != NAVIGATE_AFTER_CREATION) {
        EXPECT_FALSE(true);  // Not reached.
      }
    } else if (url == kPopupNavPopupUrl2) {
      EXPECT_FALSE(got_popup_load_end2_);
      got_popup_load_end2_.yes();

      if (mode_ == NAVIGATE_AFTER_CREATION) {
        // Close the popup browser first.
        CloseBrowser(browser, false);
      } else {
        EXPECT_FALSE(true);  // Not reached.
      }
    } else {
      EXPECT_FALSE(true);  // Not reached.
    }
  }

  void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
    TestHandler::OnBeforeClose(browser);

    bool destroy_test = false;
    if (mode_ == ALLOW_CLOSE_POPUP_FIRST || mode_ == NAVIGATE_AFTER_CREATION) {
      // Destroy the test after the popup browser closes.
      if (browser->IsPopup()) {
        destroy_test = true;
      }
    } else if (mode_ == ALLOW_CLOSE_POPUP_LAST ||
               mode_ == DESTROY_PARENT_BEFORE_CREATION ||
               mode_ == DESTROY_PARENT_BEFORE_CREATION_FORCE ||
               mode_ == DESTROY_PARENT_DURING_CREATION ||
               mode_ == DESTROY_PARENT_DURING_CREATION_FORCE ||
               mode_ == DESTROY_PARENT_AFTER_CREATION ||
               mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE) {
      // Destroy the test after the main browser closes.
      if (!browser->IsPopup()) {
        destroy_test = true;
      }
    }

    if (destroy_test) {
      // This may race with OnBeforeClose() for the remaining browser.
      CefPostTask(TID_UI, base::BindOnce(&PopupNavTestHandler::DestroyTest,
                                         weak_ptr_factory_.GetWeakPtr()));
    }
  }

 private:
  void DestroyTest() override {
    EXPECT_TRUE(got_load_start_);
    EXPECT_FALSE(got_load_error_);
    EXPECT_TRUE(got_load_end_);

    // OnBeforePopup may come before or after browser destruction with the
    // DESTROY_PARENT_BEFORE_CREATION* tests and Alloy style browsers.
    if (mode_ != DESTROY_PARENT_BEFORE_CREATION &&
        mode_ != DESTROY_PARENT_BEFORE_CREATION_FORCE) {
      EXPECT_TRUE(got_on_before_popup_);
    } else if (!use_alloy_style_browser()) {
      EXPECT_FALSE(got_on_before_popup_);
    }

    if (mode_ == DESTROY_PARENT_DURING_CREATION ||
        mode_ == DESTROY_PARENT_DURING_CREATION_FORCE ||
        mode_ == DESTROY_PARENT_AFTER_CREATION ||
        mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE) {
      // Timing may not result in abort.
    } else {
      EXPECT_FALSE(got_on_before_popup_aborted_);
    }

    if (mode_ == ALLOW_CLOSE_POPUP_FIRST || mode_ == ALLOW_CLOSE_POPUP_LAST) {
      EXPECT_TRUE(got_popup_load_start_);
      EXPECT_FALSE(got_popup_load_error_);
      EXPECT_TRUE(got_popup_load_end_);
      EXPECT_FALSE(got_popup_load_start2_);
      EXPECT_FALSE(got_popup_load_error2_);
      EXPECT_FALSE(got_popup_load_end2_);
    } else if (mode_ == DENY || mode_ == DESTROY_PARENT_BEFORE_CREATION ||
               mode_ == DESTROY_PARENT_BEFORE_CREATION_FORCE ||
               mode_ == DESTROY_PARENT_DURING_CREATION ||
               mode_ == DESTROY_PARENT_DURING_CREATION_FORCE ||
               mode_ == DESTROY_PARENT_AFTER_CREATION ||
               mode_ == DESTROY_PARENT_AFTER_CREATION_FORCE) {
      EXPECT_FALSE(got_popup_load_start_);
      EXPECT_FALSE(got_popup_load_error_);
      EXPECT_FALSE(got_popup_load_end_);
      EXPECT_FALSE(got_popup_load_start2_);
      EXPECT_FALSE(got_popup_load_error2_);
      EXPECT_FALSE(got_popup_load_end2_);
    } else if (mode_ == NAVIGATE_AFTER_CREATION) {
      EXPECT_FALSE(got_popup_load_start_);

      // With browser-side navigation we will never actually begin the
      // navigation to the 1st popup URL, so there will be no load error.
      EXPECT_FALSE(got_popup_load_error_);

      EXPECT_FALSE(got_popup_load_end_);
      EXPECT_TRUE(got_popup_load_start2_);
      EXPECT_FALSE(got_popup_load_error2_);
      EXPECT_TRUE(got_popup_load_end2_);
    }

    // Invalidate WeakPtrs now as |this| may be deleted on a different thread.
    weak_ptr_factory_.InvalidateWeakPtrs();

    // Will trigger destruction of any remaining browsers.
    TestHandler::DestroyTest();

    // Allow the the test to complete once all browsers are gone.
    SignalTestCompletion();
  }

  const TestMode mode_;
  const TestRequestContextMode rc_mode_;
  const std::string rc_cache_path_;

  TrackCallback got_on_before_popup_;
  int opener_browser_id_ = 0;
  TrackCallback got_on_before_popup_aborted_;
  TrackCallback got_load_start_;
  TrackCallback got_load_error_;
  TrackCallback got_load_end_;
  TrackCallback got_popup_load_start_;
  TrackCallback got_popup_load_error_;
  TrackCallback got_popup_load_end_;
  TrackCallback got_popup_load_start2_;
  TrackCallback got_popup_load_error2_;
  TrackCallback got_popup_load_end2_;

  base::WeakPtrFactory<PopupNavTestHandler> weak_ptr_factory_{this};

  IMPLEMENT_REFCOUNTING(PopupNavTestHandler);
};

}  // namespace
#define POPUP_TEST_GROUP(test_name, test_mode)                     \
  RC_TEST_GROUP_IN_MEMORY(RequestContextTest, PopupNav##test_name, \
                          PopupNavTestHandler, test_mode)

// Test allowing popups and closing the popup browser first.
POPUP_TEST_GROUP(AllowClosePopupFirst, ALLOW_CLOSE_POPUP_FIRST)

// Test allowing popups and closing the main browser first to verify
// that internal objects are tracked correctly (see issue #2162).
POPUP_TEST_GROUP(AllowClosePopupLast, ALLOW_CLOSE_POPUP_LAST)

// Test denying popups.
POPUP_TEST_GROUP(Deny, DENY)

// Test navigation to a different origin after popup creation to
// verify that internal objects are tracked correctly (see issue
// #1392).
POPUP_TEST_GROUP(NavigateAfterCreation, NAVIGATE_AFTER_CREATION)

// Test destroying the parent browser during or immediately after
// popup creation to verify that internal objects are tracked
// correctly (see issue #2041).
POPUP_TEST_GROUP(DestroyParentBeforeCreation, DESTROY_PARENT_BEFORE_CREATION)
POPUP_TEST_GROUP(DestroyParentBeforeCreationForce,
                 DESTROY_PARENT_BEFORE_CREATION_FORCE)
POPUP_TEST_GROUP(DestroyParentDuringCreation, DESTROY_PARENT_DURING_CREATION)
POPUP_TEST_GROUP(DestroyParentDuringCreationForce,
                 DESTROY_PARENT_DURING_CREATION_FORCE)
POPUP_TEST_GROUP(DestroyParentAfterCreation, DESTROY_PARENT_AFTER_CREATION)
POPUP_TEST_GROUP(DestroyParentAfterCreationForce,
                 DESTROY_PARENT_AFTER_CREATION_FORCE)

namespace {

const char kResolveOrigin[] = "https://www.google.com";

class MethodTestHandler : public TestHandler {
 public:
  enum Method {
    METHOD_CLEAR_CERTIFICATE_EXCEPTIONS,
    METHOD_CLOSE_ALL_CONNECTIONS,
    METHOD_RESOLVE_HOST,
  };

  class CompletionCallback : public CefCompletionCallback,
                             public CefResolveCallback {
   public:
    CompletionCallback(MethodTestHandler* test_handler,
                       CefRefPtr<CefBrowser> browser)
        : test_handler_(test_handler), browser_(browser) {}

    ~CompletionCallback() override {
      // OnComplete should be executed.
      EXPECT_FALSE(test_handler_);
    }

    void OnComplete() override {
      EXPECT_UI_THREAD();

      // OnComplete should be executed only one time.
      EXPECT_TRUE(test_handler_);
      test_handler_->OnCompleteCallback(browser_);
      test_handler_ = nullptr;
      browser_ = nullptr;
    }

    void OnResolveCompleted(
        cef_errorcode_t result,
        const std::vector<CefString>& resolved_ips) override {
      EXPECT_EQ(ERR_NONE, result);
      EXPECT_TRUE(!resolved_ips.empty());
      OnComplete();
    }

   private:
    MethodTestHandler* test_handler_;
    CefRefPtr<CefBrowser> browser_;

    IMPLEMENT_REFCOUNTING(CompletionCallback);
  };

  MethodTestHandler(bool global_context, Method method)
      : global_context_(global_context), method_(method) {}

  void RunTest() override {
    const char kUrl[] = "https://tests/method.html";

    AddResource(kUrl, "<html><body>Method</body></html>", "text/html");

    CefRefPtr<CefRequestContext> request_context;
    if (!global_context_) {
      CefRequestContextSettings settings;
      request_context = CefRequestContext::CreateContext(settings, nullptr);
    }

    CreateBrowser(kUrl, request_context);

    // Time out the test after a reasonable period of time.
    SetTestTimeout();
  }

  void OnLoadEnd(CefRefPtr<CefBrowser> browser,
                 CefRefPtr<CefFrame> frame,
                 int httpStatusCode) override {
    CefRefPtr<CefRequestContext> context =
        browser->GetHost()->GetRequestContext();
    CefRefPtr<CompletionCallback> callback =
        new CompletionCallback(this, browser);
    if (method_ == METHOD_CLEAR_CERTIFICATE_EXCEPTIONS) {
      context->ClearCertificateExceptions(callback);
    } else if (method_ == METHOD_CLOSE_ALL_CONNECTIONS) {
      context->CloseAllConnections(callback);
    } else if (method_ == METHOD_RESOLVE_HOST) {
      context->ResolveHost(kResolveOrigin, callback);
    }
  }

  void OnCompleteCallback(CefRefPtr<CefBrowser> browser) {
    EXPECT_UI_THREAD();
    EXPECT_FALSE(got_completion_callback_);
    got_completion_callback_.yes();

    DestroyTest();
  }

 private:
  void DestroyTest() override {
    EXPECT_TRUE(got_completion_callback_);
    TestHandler::DestroyTest();
  }

  const bool global_context_;
  const Method method_;

  TrackCallback got_completion_callback_;

  IMPLEMENT_REFCOUNTING(MethodTestHandler);
};

}  // namespace

// Test CefRequestContext::ClearCertificateExceptions with the global
// context.
TEST(RequestContextTest, ClearCertificateExceptionsGlobal) {
  CefRefPtr<MethodTestHandler> handler = new MethodTestHandler(
      true, MethodTestHandler::METHOD_CLEAR_CERTIFICATE_EXCEPTIONS);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test CefRequestContext::ClearCertificateExceptions with a custom
// context.
TEST(RequestContextTest, ClearCertificateExceptionsCustom) {
  CefRefPtr<MethodTestHandler> handler = new MethodTestHandler(
      false, MethodTestHandler::METHOD_CLEAR_CERTIFICATE_EXCEPTIONS);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test CefRequestContext::CloseAllConnections with the global
// context.
TEST(RequestContextTest, CloseAllConnectionsGlobal) {
  CefRefPtr<MethodTestHandler> handler = new MethodTestHandler(
      true, MethodTestHandler::METHOD_CLOSE_ALL_CONNECTIONS);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test CefRequestContext::CloseAllConnections with a custom context.
TEST(RequestContextTest, CloseAllConnectionsCustom) {
  CefRefPtr<MethodTestHandler> handler = new MethodTestHandler(
      false, MethodTestHandler::METHOD_CLOSE_ALL_CONNECTIONS);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test CefRequestContext::ResolveHost with the global context.
TEST(RequestContextTest, ResolveHostGlobal) {
  CefRefPtr<MethodTestHandler> handler =
      new MethodTestHandler(true, MethodTestHandler::METHOD_RESOLVE_HOST);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test CefRequestContext::ResolveHost with a custom context.
TEST(RequestContextTest, ResolveHostCustom) {
  CefRefPtr<MethodTestHandler> handler =
      new MethodTestHandler(false, MethodTestHandler::METHOD_RESOLVE_HOST);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}
