// 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_bind.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/gtest/include/gtest/gtest.h"

TEST(RequestContextTest, GetGlobalContext) {
  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, CreateContext) {
  class Handler : public CefRequestContextHandler {
   public:
    Handler() {}
    CefRefPtr<CefCookieManager> GetCookieManager() override { return NULL; }
   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, CreateContextNoHandler) {
  CefRequestContextSettings settings;

  CefRefPtr<CefRequestContext> context1 =
      CefRequestContext::CreateContext(settings, NULL);
  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, NULL);
  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, CreateContextSharedGlobal) {
  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));

  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(context1, NULL);
  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, NULL);
  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, NULL);
  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));
}

TEST(RequestContextTest, CreateContextSharedOnDisk) {
  CefScopedTempDir tempdir;
  EXPECT_TRUE(tempdir.CreateUniqueTempDir());

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

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

  CefRefPtr<CefRequestContext> context2 =
      CefRequestContext::CreateContext(context1, NULL);
  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, NULL);
  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, NULL);
  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 CookieTestHandler : public TestHandler {
 public:
  class RequestContextHandler : public CefRequestContextHandler {
   public:
    explicit RequestContextHandler(CookieTestHandler* handler)
        : handler_(handler) {}

    CefRefPtr<CefCookieManager> GetCookieManager() override {
      EXPECT_TRUE(handler_);
      handler_->got_get_cookie_manager_.yes();
      return handler_->cookie_manager_;
    }

    void Detach() {
      handler_ = NULL;
    }

   private:
    CookieTestHandler* handler_;

    IMPLEMENT_REFCOUNTING(RequestContextHandler);
  };

  CookieTestHandler(const std::string& url)
      : url_(url) {}

  void RunTest() override {
    AddResource(url_,
        "<html>"
        "<head><script>document.cookie='name1=value1';</script></head>"
        "<body>Nav1</body>"
        "</html>", "text/html");

    CefRequestContextSettings settings;

    context_handler_ = new RequestContextHandler(this);
    context_ =
        CefRequestContext::CreateContext(settings, context_handler_.get());
    cookie_manager_ = CefCookieManager::CreateManager(CefString(), true, NULL);

    // 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_EQ(context->GetHandler().get(), context_handler_.get());

    FinishTest();
  }

 protected:
  void FinishTest() {
    // Verify that the cookie was set correctly.
    class TestVisitor : public CefCookieVisitor {
     public:
      explicit TestVisitor(CookieTestHandler* handler)
          : handler_(handler) {
      }
      ~TestVisitor() override {
        // Destroy the test.
        CefPostTask(TID_UI,
            base::Bind(&CookieTestHandler::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_cookie_.yes();
        return true;
      }

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

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

  void DestroyTest() override {
    // Verify test expectations.
    EXPECT_TRUE(got_get_cookie_manager_);
    EXPECT_TRUE(got_cookie_);

    context_handler_->Detach();
    context_handler_ = NULL;
    context_ = NULL;
    
    TestHandler::DestroyTest();
  }

  std::string url_;
  CefRefPtr<CefRequestContext> context_;
  CefRefPtr<RequestContextHandler> context_handler_;
  CefRefPtr<CefCookieManager> cookie_manager_;

  TrackCallback got_get_cookie_manager_;
  TrackCallback got_cookie_;

  IMPLEMENT_REFCOUNTING(CookieTestHandler);
};

}  // namespace

// Test that the cookie manager is retrieved via the associated request context.
TEST(RequestContextTest, GetCookieManager) {
  CefRefPtr<CookieTestHandler> handler =
      new CookieTestHandler(
          "http://tests-simple-rch.com/nav1.html");
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

class PopupTestHandler : public TestHandler {
 public:
  class RequestContextHandler : public CefRequestContextHandler {
   public:
    explicit RequestContextHandler(PopupTestHandler* handler)
        : handler_(handler) {}

    CefRefPtr<CefCookieManager> GetCookieManager() override {
      EXPECT_TRUE(handler_);
      if (url_ == handler_->url_)
        handler_->got_get_cookie_manager1_.yes();
      else if (url_ == handler_->popup_url_)
        handler_->got_get_cookie_manager2_.yes();
      return handler_->cookie_manager_;
    }

    void SetURL(const std::string& url) {
      url_ = url;
    }

    void Detach() {
      handler_ = NULL;
    }

   private:
    PopupTestHandler* handler_;
    std::string url_;

    IMPLEMENT_REFCOUNTING(RequestContextHandler);
  };

  enum Mode {
    MODE_WINDOW_OPEN,
    MODE_TARGETED_LINK,
    MODE_NOREFERRER_LINK,
  };

  PopupTestHandler(bool same_origin,
                   Mode mode)
      : mode_(mode) {
    url_ = "http://tests-simple-rch1.com/nav1.html";
    if (same_origin)
      popup_url_ = "http://tests-simple-rch1.com/pop1.html";
    else
      popup_url_ = "http://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_handler_ = new RequestContextHandler(this);
    context_handler_->SetURL(url_);
    context_ =
        CefRequestContext::CreateContext(settings, context_handler_.get());
    cookie_manager_ = CefCookieManager::CreateManager(CefString(), true, NULL);

    // 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();
      context_handler_->SetURL(popup_url_);
      LaunchPopup(browser);
    } 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,
                     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,
                     bool* no_javascript_access) override {
    got_on_before_popup_.yes();

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

    EXPECT_EQ(WOD_NEW_FOREGROUND_TAB, target_disposition);

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

    return false;
  }

  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;
      browser->GetHost()->SendMouseClickEvent(
          mouse_event, MBT_LEFT, false, 1);
      browser->GetHost()->SendMouseClickEvent(
          mouse_event, MBT_LEFT, true, 1);
    } else {
      EXPECT_TRUE(false);  // 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::Bind(&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();
        else if (name == "name2" && value == "value2")
          handler_->got_cookie2_.yes();
        return true;
      }

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

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

  void DestroyTest() override {
    // Verify test expectations.
    EXPECT_TRUE(got_get_cookie_manager1_);
    EXPECT_TRUE(got_load_end1_);
    EXPECT_TRUE(got_on_before_popup_);
    EXPECT_TRUE(got_get_cookie_manager2_);
    EXPECT_TRUE(got_load_end2_);
    EXPECT_TRUE(got_cookie1_);
    EXPECT_TRUE(got_cookie2_);

    context_handler_->Detach();
    context_handler_ = NULL;
    context_ = NULL;
    
    TestHandler::DestroyTest();
  }

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

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

  TrackCallback got_get_cookie_manager1_;
  TrackCallback got_load_end1_;
  TrackCallback got_on_before_popup_;
  TrackCallback got_get_cookie_manager2_;
  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, WindowOpenSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true,
          PopupTestHandler::MODE_WINDOW_OPEN);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

TEST(RequestContextTest, WindowOpenDifferentOrigin) {
  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, TargetedLinkSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true,
          PopupTestHandler::MODE_TARGETED_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

TEST(RequestContextTest, TargetedLinkDifferentOrigin) {
  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, NoReferrerLinkSameOrigin) {
  CefRefPtr<PopupTestHandler> handler =
      new PopupTestHandler(true,
          PopupTestHandler::MODE_NOREFERRER_LINK);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

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


namespace {

const char kResolveOrigin[] = "http://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 {
      EXPECT_UI_THREAD();

      // 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[] = "http://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();

    if (method_ == METHOD_RESOLVE_HOST) {
      // Now try a cached request.
      CefPostTask(TID_IO,
          base::Bind(&MethodTestHandler::ResolveHostCached, this, browser));
    } else {
      DestroyTest();
    }
  }

  void ResolveHostCached(CefRefPtr<CefBrowser> browser) {
    EXPECT_IO_THREAD();

    CefRefPtr<CefRequestContext> context =
        browser->GetHost()->GetRequestContext();
    std::vector<CefString> resolved_ips;
    cef_errorcode_t result =
        context->ResolveHostCached(kResolveOrigin, resolved_ips);
    EXPECT_EQ(ERR_NONE, result);
    EXPECT_TRUE(!resolved_ips.empty());

    CefPostTask(TID_UI, base::Bind(&MethodTestHandler::DestroyTest, this));
  }

 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);
}
