// 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 <cstdlib>
#include <set>
#include <sstream>
#include <vector>

#include "include/base/cef_bind.h"
#include "include/base/cef_weak_ptr.h"
#include "include/cef_v8.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/ceftests/routing_test_handler.h"
#include "tests/gtest/include/gtest/gtest.h"
#include "tests/shared/renderer/client_app_renderer.h"

using client::ClientAppRenderer;

namespace {

const char kTestDomainRoot[] = "http://tests-mr";
const char kTestDomain1[] = "http://tests-mr1.com/";
const char kTestDomain2[] = "http://tests-mr2.com/";
const char kTestDomain3[] = "http://tests-mr3.com/";

const char kDoneMessageName[] = "mrtNotifyMsg";

const char kJSNotifyFunc[] = "mrtNotify";
const char kJSAssertTotalCountFunc[] = "mrtAssertTotalCount";
const char kJSAssertBrowserCountFunc[] = "mrtAssertBrowserCount";
const char kJSAssertContextCountFunc[] = "mrtAssertContextCount";

void SetRouterConfig(CefMessageRouterConfig& config) {
  config.js_query_function = "mrtQuery";
  config.js_cancel_function = "mrtQueryCancel";
}

// Handle the renderer side of the routing implementation.
class MRRenderDelegate : public ClientAppRenderer::Delegate {
 public:
  class V8HandlerImpl : public CefV8Handler {
   public:
    explicit V8HandlerImpl(CefRefPtr<MRRenderDelegate> delegate)
        : delegate_(delegate) {}

    bool Execute(const CefString& name,
                 CefRefPtr<CefV8Value> object,
                 const CefV8ValueList& arguments,
                 CefRefPtr<CefV8Value>& retval,
                 CefString& exception) override {
      const std::string& message_name = name;
      if (message_name == kJSNotifyFunc) {
        EXPECT_EQ(1U, arguments.size());
        EXPECT_TRUE(arguments[0]->IsString());

        const CefString& msg = arguments[0]->GetStringValue();
        CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
        CefRefPtr<CefFrame> frame = context->GetFrame();

        CefRefPtr<CefProcessMessage> message =
            CefProcessMessage::Create(kDoneMessageName);
        CefRefPtr<CefListValue> args = message->GetArgumentList();
        args->SetString(0, msg);
        frame->SendProcessMessage(PID_BROWSER, message);
        return true;
      } else {
        EXPECT_EQ(1U, arguments.size());
        EXPECT_TRUE(arguments[0]->IsInt());

        const int expected_count = arguments[0]->GetIntValue();
        int actual_count = -1;

        CefRefPtr<CefV8Context> context = CefV8Context::GetCurrentContext();
        CefRefPtr<CefBrowser> browser = context->GetBrowser();

        if (name == kJSAssertTotalCountFunc) {
          actual_count =
              delegate_->message_router_->GetPendingCount(nullptr, nullptr);
        } else if (name == kJSAssertBrowserCountFunc) {
          actual_count =
              delegate_->message_router_->GetPendingCount(browser, nullptr);
        } else if (name == kJSAssertContextCountFunc) {
          actual_count =
              delegate_->message_router_->GetPendingCount(browser, context);
        }

        if (expected_count != actual_count) {
          std::stringstream ss;
          ss << message_name << " failed; expected " << expected_count
             << ", got " << actual_count;
          exception = ss.str();
        }
      }

      return true;
    }

   private:
    CefRefPtr<MRRenderDelegate> delegate_;

    IMPLEMENT_REFCOUNTING(V8HandlerImpl);
  };

  MRRenderDelegate() {}

  void OnWebKitInitialized(CefRefPtr<ClientAppRenderer> app) override {
    // Create the renderer-side router for query handling.
    CefMessageRouterConfig config;
    SetRouterConfig(config);
    message_router_ = CefMessageRouterRendererSide::Create(config);
  }

  void OnContextCreated(CefRefPtr<ClientAppRenderer> app,
                        CefRefPtr<CefBrowser> browser,
                        CefRefPtr<CefFrame> frame,
                        CefRefPtr<CefV8Context> context) override {
    const std::string& url = frame->GetURL();
    if (url.find(kTestDomainRoot) != 0)
      return;

    message_router_->OnContextCreated(browser, frame, context);

    // Register function handlers with the 'window' object.
    CefRefPtr<CefV8Value> window = context->GetGlobal();

    CefRefPtr<V8HandlerImpl> handler = new V8HandlerImpl(this);
    CefV8Value::PropertyAttribute attributes =
        static_cast<CefV8Value::PropertyAttribute>(
            V8_PROPERTY_ATTRIBUTE_READONLY | V8_PROPERTY_ATTRIBUTE_DONTENUM |
            V8_PROPERTY_ATTRIBUTE_DONTDELETE);

    CefRefPtr<CefV8Value> notify_func =
        CefV8Value::CreateFunction(kJSNotifyFunc, handler.get());
    window->SetValue(kJSNotifyFunc, notify_func, attributes);

    CefRefPtr<CefV8Value> total_count_func =
        CefV8Value::CreateFunction(kJSAssertTotalCountFunc, handler.get());
    window->SetValue(kJSAssertTotalCountFunc, total_count_func, attributes);

    CefRefPtr<CefV8Value> browser_count_func =
        CefV8Value::CreateFunction(kJSAssertBrowserCountFunc, handler.get());
    window->SetValue(kJSAssertBrowserCountFunc, browser_count_func, attributes);

    CefRefPtr<CefV8Value> context_count_func =
        CefV8Value::CreateFunction(kJSAssertContextCountFunc, handler.get());
    window->SetValue(kJSAssertContextCountFunc, context_count_func, attributes);
  }

  void OnContextReleased(CefRefPtr<ClientAppRenderer> app,
                         CefRefPtr<CefBrowser> browser,
                         CefRefPtr<CefFrame> frame,
                         CefRefPtr<CefV8Context> context) override {
    const std::string& url = frame->GetURL();
    if (url.find(kTestDomainRoot) != 0)
      return;

    message_router_->OnContextReleased(browser, frame, context);
  }

  bool OnProcessMessageReceived(CefRefPtr<ClientAppRenderer> app,
                                CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame,
                                CefProcessId source_process,
                                CefRefPtr<CefProcessMessage> message) override {
    const std::string& url = frame->GetURL();
    if (url.find(kTestDomainRoot) != 0)
      return false;

    return message_router_->OnProcessMessageReceived(browser, frame,
                                                     source_process, message);
  }

 private:
  CefRefPtr<CefMessageRouterRendererSide> message_router_;

  IMPLEMENT_REFCOUNTING(MRRenderDelegate);
};

}  // namespace

// Entry point for creating the test delegate.
// Called from client_app_delegates.cc.
void CreateMessageRouterRendererTests(
    ClientAppRenderer::DelegateSet& delegates) {
  delegates.insert(new MRRenderDelegate);
}

namespace {

class MRTestHandler : public TestHandler {
 public:
  MRTestHandler() {}

  void RunTest() override {
    RunMRTest();

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

  void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
    if (!message_router_.get()) {
      // Create the browser-side router for query handling.
      CefMessageRouterConfig config;
      SetRouterConfig(config);
      message_router_ = CefMessageRouterBrowserSide::Create(config);
      AddHandlers(message_router_);
    }
    TestHandler::OnAfterCreated(browser);
  }

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

  void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
                                 TerminationStatus status) override {
    message_router_->OnRenderProcessTerminated(browser);
  }

  // Only call this method if the navigation isn't canceled.
  bool OnBeforeBrowse(CefRefPtr<CefBrowser> browser,
                      CefRefPtr<CefFrame> frame,
                      CefRefPtr<CefRequest> request,
                      bool user_gesture,
                      bool is_redirect) override {
    message_router_->OnBeforeBrowse(browser, frame);
    return false;
  }

  // Returns true if the router handled the navigation.
  bool OnProcessMessageReceived(CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame,
                                CefProcessId source_process,
                                CefRefPtr<CefProcessMessage> message) override {
    const std::string& message_name = message->GetName();
    if (message_name == kDoneMessageName) {
      CefRefPtr<CefListValue> args = message->GetArgumentList();
      EXPECT_EQ(1U, args->GetSize());
      EXPECT_EQ(VTYPE_STRING, args->GetType(0));
      OnNotify(browser, frame, args->GetString(0));
      return true;
    }

    return message_router_->OnProcessMessageReceived(browser, frame,
                                                     source_process, message);
  }

  CefRefPtr<CefMessageRouterBrowserSide> GetRouter() const {
    return message_router_;
  }

 protected:
  virtual void RunMRTest() = 0;

  virtual void AddHandlers(
      CefRefPtr<CefMessageRouterBrowserSide> message_router) = 0;

  virtual void OnNotify(CefRefPtr<CefBrowser> browser,
                        CefRefPtr<CefFrame> frame,
                        const std::string& message) = 0;

  bool AssertQueryCount(CefRefPtr<CefBrowser> browser,
                        CefMessageRouterBrowserSide::Handler* handler,
                        int expected_count) {
    int actual_count = message_router_->GetPendingCount(browser, handler);
    EXPECT_EQ(expected_count, actual_count);
    return (expected_count == actual_count);
  }

  void AssertMainBrowser(CefRefPtr<CefBrowser> browser) {
    EXPECT_TRUE(browser.get());
    EXPECT_EQ(GetBrowserId(), browser->GetIdentifier());
  }

 private:
  CefRefPtr<CefMessageRouterBrowserSide> message_router_;

  IMPLEMENT_REFCOUNTING(MRTestHandler);
};

// Implementation of MRTestHandler that loads a single page.
class SingleLoadTestHandler : public MRTestHandler,
                              public CefMessageRouterBrowserSide::Handler {
 public:
  SingleLoadTestHandler()
      : main_url_(std::string(kTestDomain1) + "main.html") {}

  const std::string& GetMainURL() { return main_url_; }

 protected:
  void RunMRTest() override {
    AddOtherResources();
    AddResource(main_url_, GetMainHTML(), "text/html");

    CreateBrowser(main_url_, nullptr);
  }

  void AddHandlers(
      CefRefPtr<CefMessageRouterBrowserSide> message_router) override {
    message_router->AddHandler(this, false);
  }

  virtual void AddOtherResources() {}

  virtual std::string GetMainHTML() = 0;

  void AssertMainFrame(CefRefPtr<CefFrame> frame) {
    EXPECT_TRUE(frame.get());
    EXPECT_TRUE(frame->IsMain());
    EXPECT_STREQ(main_url_.c_str(), frame->GetURL().ToString().c_str());
  }

 private:
  const std::string main_url_;
};

// Used to verify that the test harness (bound functions) behave correctly.
class HarnessTestHandler : public SingleLoadTestHandler {
 public:
  HarnessTestHandler(bool test_success) : test_success_(test_success) {}

  std::string GetMainHTML() override {
    std::string html;
    if (test_success_) {
      // All assertions should pass.
      html =
          "<html><body><script>\n"
          "var fail_ct = 0;\n"
          "try { window.mrtAssertTotalCount(0); } catch (e) { fail_ct++; }\n"
          "try { window.mrtAssertBrowserCount(0); } catch (e) { fail_ct++; }\n"
          "try { window.mrtAssertContextCount(0); } catch (e) { fail_ct++; }\n"
          "window.mrtNotify('' + (fail_ct == 0));"
          "</script></body></html>";
    } else {
      // All assertions should fail.
      html =
          "<html><body><script>\n"
          "var fail_ct = 0;\n"
          "try { window.mrtAssertTotalCount(1); } catch (e) { fail_ct++; }\n"
          "try { window.mrtAssertBrowserCount(1); } catch (e) { fail_ct++; }\n"
          "try { window.mrtAssertContextCount(1); } catch (e) { fail_ct++; }\n"
          "window.mrtNotify('' + (fail_ct == 3));"
          "</script></body></html>";
    }
    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    got_done_.yes();
    EXPECT_STREQ("true", message.c_str());
    DestroyTest();
  }

  void DestroyTest() override {
    EXPECT_TRUE(got_done_);
    TestHandler::DestroyTest();
  }

 private:
  const bool test_success_;
  TrackCallback got_done_;
};

}  // namespace

// Verify that the test harness works with successful assertions.
TEST(MessageRouterTest, HarnessSuccess) {
  CefRefPtr<HarnessTestHandler> handler = new HarnessTestHandler(true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Verify that the test harness works with failed assertions.
TEST(MessageRouterTest, HarnessFailure) {
  CefRefPtr<HarnessTestHandler> handler = new HarnessTestHandler(false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

const char kSingleQueryRequest[] = "request_context";
const char kSingleQueryResponse[] = "success_response";
const int kSingleQueryErrorCode = 5;
const char kSingleQueryErrorMessage[] = "error_message";

// Test a single query in a single page load.
class SingleQueryTestHandler : public SingleLoadTestHandler {
 public:
  enum TestType {
    SUCCESS,
    FAILURE,
    CANCEL,
  };

  SingleQueryTestHandler(TestType type, bool sync_callback)
      : test_type_(type), sync_callback_(sync_callback), query_id_(0) {}

  std::string GetMainHTML() override {
    std::string html;

    std::stringstream ss;
    ss << kSingleQueryErrorCode;
    const std::string& errorCodeStr = ss.str();

    html =
        "<html><body><script>\n"
        // No requests should exist.
        "window.mrtAssertTotalCount(0);\n"
        "window.mrtAssertBrowserCount(0);\n"
        "window.mrtAssertContextCount(0);\n"
        // Send the query.
        "var request_id = window.mrtQuery({\n"
        "  request: '" +
        std::string(kSingleQueryRequest) +
        "',\n"
        "  persistent: false,\n"
        "  onSuccess: function(response) {\n"
        // Request should be removed before callback is executed.
        "    window.mrtAssertTotalCount(0);\n"
        "    window.mrtAssertBrowserCount(0);\n"
        "    window.mrtAssertContextCount(0);\n"
        "    if (response == '" +
        std::string(kSingleQueryResponse) +
        "')\n"
        "      window.mrtNotify('success');\n"
        "    else\n"
        "      window.mrtNotify('error-onSuccess');\n"
        "  },\n"
        "  onFailure: function(error_code, error_message) {\n"
        // Request should be removed before callback is executed.
        "    window.mrtAssertTotalCount(0);\n"
        "    window.mrtAssertBrowserCount(0);\n"
        "    window.mrtAssertContextCount(0);\n"
        "    if (error_code == " +
        errorCodeStr + " && error_message == '" +
        std::string(kSingleQueryErrorMessage) +
        "')\n"
        "      window.mrtNotify('failure');\n"
        "    else\n"
        "      window.mrtNotify('error-onFailure');\n"
        "  }\n"
        "});\n"
        // Request should exist.
        "window.mrtAssertTotalCount(1);\n"
        "window.mrtAssertBrowserCount(1);\n"
        "window.mrtAssertContextCount(1);\n";

    if (test_type_ == CANCEL) {
      html +=
          "window.mrtQueryCancel(request_id);\n"
          // Request should be removed immediately.
          "window.mrtAssertTotalCount(0);\n"
          "window.mrtAssertBrowserCount(0);\n"
          "window.mrtAssertContextCount(0);\n"
          "window.mrtNotify('cancel');\n";
    }

    html += "</script></body></html>";
    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    // OnNotify only be called once.
    EXPECT_FALSE(got_notify_);
    got_notify_.yes();

    if (test_type_ == SUCCESS) {
      EXPECT_STREQ("success", message.c_str());
    } else if (test_type_ == FAILURE) {
      EXPECT_STREQ("failure", message.c_str());
    } else if (test_type_ == CANCEL) {
      EXPECT_STREQ("cancel", message.c_str());
    }

    DestroyTestIfDone();
  }

  void ExecuteCallback() {
    EXPECT_TRUE(callback_.get());
    if (test_type_ == SUCCESS) {
      callback_->Success(kSingleQueryResponse);
    } else if (test_type_ == FAILURE) {
      callback_->Failure(kSingleQueryErrorCode, kSingleQueryErrorMessage);
    } else {
      EXPECT_TRUE(false);  // Not reached.
    }
    callback_ = nullptr;
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_NE(0, query_id);
    EXPECT_FALSE(persistent);
    EXPECT_STREQ(kSingleQueryRequest, request.ToString().c_str());

    got_on_query_.yes();

    query_id_ = query_id;
    callback_ = callback;

    if (test_type_ == SUCCESS || test_type_ == FAILURE) {
      if (sync_callback_) {
        ExecuteCallback();
      } else {
        CefPostTask(TID_UI,
                    base::Bind(&SingleQueryTestHandler::ExecuteCallback, this));
      }
    }

    return true;
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_EQ(test_type_, CANCEL);
    EXPECT_EQ(query_id_, query_id);
    EXPECT_TRUE(got_on_query_);
    EXPECT_TRUE(callback_.get());

    got_on_query_canceled_.yes();
    callback_ = nullptr;

    DestroyTestIfDone();
  }

  void DestroyTestIfDone() {
    bool destroy_test = false;
    if (test_type_ == CANCEL)
      destroy_test = got_notify_ && got_on_query_canceled_;
    else
      destroy_test = got_notify_;
    if (destroy_test)
      DestroyTest();
  }

  void DestroyTest() override {
    EXPECT_TRUE(got_notify_);
    EXPECT_TRUE(got_on_query_);
    EXPECT_FALSE(callback_.get());

    if (test_type_ == CANCEL)
      EXPECT_TRUE(got_on_query_canceled_);
    else
      EXPECT_FALSE(got_on_query_canceled_);

    TestHandler::DestroyTest();
  }

 private:
  const TestType test_type_;
  const bool sync_callback_;

  int64 query_id_;
  CefRefPtr<Callback> callback_;

  TrackCallback got_on_query_;
  TrackCallback got_on_query_canceled_;
  TrackCallback got_notify_;
};

}  // namespace

// Test that a single query with successful result delivered synchronously.
TEST(MessageRouterTest, SingleQuerySuccessSyncCallback) {
  CefRefPtr<SingleQueryTestHandler> handler =
      new SingleQueryTestHandler(SingleQueryTestHandler::SUCCESS, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with successful result delivered asynchronously.
TEST(MessageRouterTest, SingleQuerySuccessAsyncCallback) {
  CefRefPtr<SingleQueryTestHandler> handler =
      new SingleQueryTestHandler(SingleQueryTestHandler::SUCCESS, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with failure result delivered synchronously.
TEST(MessageRouterTest, SingleQueryFailureSyncCallback) {
  CefRefPtr<SingleQueryTestHandler> handler =
      new SingleQueryTestHandler(SingleQueryTestHandler::FAILURE, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with failure result delivered asynchronously.
TEST(MessageRouterTest, SingleQueryFailureAsyncCallback) {
  CefRefPtr<SingleQueryTestHandler> handler =
      new SingleQueryTestHandler(SingleQueryTestHandler::FAILURE, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with cancellation.
TEST(MessageRouterTest, SingleQueryCancel) {
  CefRefPtr<SingleQueryTestHandler> handler =
      new SingleQueryTestHandler(SingleQueryTestHandler::CANCEL, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

const int kSinglePersistentQueryResponseCount = 10;

// Test a single persistent query in a single page load.
class SinglePersistentQueryTestHandler : public SingleLoadTestHandler {
 public:
  enum TestType {
    SUCCESS,
    FAILURE,
  };

  SinglePersistentQueryTestHandler(TestType test_type, bool sync_callback)
      : test_type_(test_type), sync_callback_(sync_callback), query_id_(0) {}

  std::string GetMainHTML() override {
    std::string html;

    std::stringstream ss;
    ss << kSinglePersistentQueryResponseCount;
    const std::string& responseCountStr = ss.str();
    ss.str("");
    ss << kSingleQueryErrorCode;
    const std::string& errorCodeStr = ss.str();

    html =
        "<html><body><script>\n"
        // No requests should exist.
        "window.mrtAssertTotalCount(0);\n"
        "window.mrtAssertBrowserCount(0);\n"
        "window.mrtAssertContextCount(0);\n"
        // Keep track of the number of responses.
        "var count = 0;\n"
        // Send the query.
        "var request_id = window.mrtQuery({\n"
        "  request: '" +
        std::string(kSingleQueryRequest) +
        "',\n"
        "  persistent: true,\n"
        "  onSuccess: function(response) {\n"
        // Request should not be removed.
        "    window.mrtAssertTotalCount(1);\n"
        "    window.mrtAssertBrowserCount(1);\n"
        "    window.mrtAssertContextCount(1);\n"
        "    if (response == '" +
        std::string(kSingleQueryResponse) +
        "') {\n"
        "      if (++count == " +
        responseCountStr +
        ") {\n"
        "        window.mrtNotify('success');\n"
        "        window.mrtQueryCancel(request_id);\n"
        // Request should be removed immediately.
        "        window.mrtAssertTotalCount(0);\n"
        "        window.mrtAssertBrowserCount(0);\n"
        "        window.mrtAssertContextCount(0);\n"
        "      }\n"
        "    } else {\n"
        "      window.mrtNotify('error-onSuccess');\n"
        "    }\n"
        "  },\n"
        "  onFailure: function(error_code, error_message) {\n"
        // Request should be removed before callback is executed.
        "    window.mrtAssertTotalCount(0);\n"
        "    window.mrtAssertBrowserCount(0);\n"
        "    window.mrtAssertContextCount(0);\n"
        "    if (error_code == " +
        errorCodeStr + " && error_message == '" +
        std::string(kSingleQueryErrorMessage) +
        "') {\n"
        "      window.mrtNotify('failure');\n"
        "    } else {\n"
        "      window.mrtNotify('error-onFailure');\n"
        "    }\n"
        "  }\n"
        "});\n"
        // Request should exist.
        "window.mrtAssertTotalCount(1);\n"
        "window.mrtAssertBrowserCount(1);\n"
        "window.mrtAssertContextCount(1);\n";

    html += "</script></body></html>";
    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    if (test_type_ == SUCCESS) {
      EXPECT_STREQ("success", message.c_str());
    } else if (test_type_ == FAILURE) {
      EXPECT_STREQ("failure", message.c_str());
    }

    got_notify_.yes();

    DestroyTestIfDone();
  }

  void ExecuteCallback() {
    EXPECT_TRUE(callback_.get());
    if (test_type_ == SUCCESS) {
      callback_->Success(kSingleQueryResponse);
    } else {
      callback_->Failure(kSingleQueryErrorCode, kSingleQueryErrorMessage);
      callback_ = nullptr;
    }
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_NE(0, query_id);
    EXPECT_TRUE(persistent);
    EXPECT_STREQ(kSingleQueryRequest, request.ToString().c_str());

    got_on_query_.yes();

    query_id_ = query_id;
    callback_ = callback;

    int repeat =
        (test_type_ == SUCCESS ? kSinglePersistentQueryResponseCount : 1);

    for (int i = 0; i < repeat; ++i) {
      if (sync_callback_) {
        ExecuteCallback();
      } else {
        CefPostTask(
            TID_UI,
            base::Bind(&SinglePersistentQueryTestHandler::ExecuteCallback,
                       this));
      }
    }

    return true;
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_EQ(query_id_, query_id);
    EXPECT_TRUE(got_on_query_);
    EXPECT_TRUE(callback_.get());

    got_on_query_canceled_.yes();
    callback_ = nullptr;

    DestroyTestIfDone();
  }

  void DestroyTestIfDone() {
    bool destroy_test = false;
    if (test_type_ == SUCCESS) {
      if (got_on_query_ && got_on_query_canceled_ && got_notify_)
        destroy_test = true;
    } else if (got_on_query_ && got_notify_) {
      destroy_test = true;
    }

    if (destroy_test)
      DestroyTest();
  }

  void DestroyTest() override {
    EXPECT_TRUE(got_notify_);
    EXPECT_TRUE(got_on_query_);
    EXPECT_FALSE(callback_.get());

    if (test_type_ == SUCCESS)
      EXPECT_TRUE(got_on_query_canceled_);
    else
      EXPECT_FALSE(got_on_query_canceled_);

    TestHandler::DestroyTest();
  }

 private:
  const TestType test_type_;
  const bool sync_callback_;

  int64 query_id_;
  CefRefPtr<Callback> callback_;

  TrackCallback got_on_query_;
  TrackCallback got_on_query_canceled_;
  TrackCallback got_notify_;
};

}  // namespace

// Test that a single query with successful result delivered synchronously.
TEST(MessageRouterTest, SinglePersistentQuerySuccessSyncCallback) {
  CefRefPtr<SinglePersistentQueryTestHandler> handler =
      new SinglePersistentQueryTestHandler(
          SinglePersistentQueryTestHandler::SUCCESS, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with successful result delivered asynchronously.
TEST(MessageRouterTest, SinglePersistentQuerySuccessAsyncCallback) {
  CefRefPtr<SinglePersistentQueryTestHandler> handler =
      new SinglePersistentQueryTestHandler(
          SinglePersistentQueryTestHandler::SUCCESS, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with failure result delivered synchronously.
TEST(MessageRouterTest, SinglePersistentQueryFailureSyncCallback) {
  CefRefPtr<SinglePersistentQueryTestHandler> handler =
      new SinglePersistentQueryTestHandler(
          SinglePersistentQueryTestHandler::FAILURE, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that a single query with failure result delivered asynchronously.
TEST(MessageRouterTest, SinglePersistentQueryFailureAsyncCallback) {
  CefRefPtr<SinglePersistentQueryTestHandler> handler =
      new SinglePersistentQueryTestHandler(
          SinglePersistentQueryTestHandler::FAILURE, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

// Test a single unhandled query in a single page load.
class SingleUnhandledQueryTestHandler : public SingleLoadTestHandler {
 public:
  SingleUnhandledQueryTestHandler() {}

  std::string GetMainHTML() override {
    std::string html;

    html =
        "<html><body><script>\n"
        // No requests should exist.
        "window.mrtAssertTotalCount(0);\n"
        "window.mrtAssertBrowserCount(0);\n"
        "window.mrtAssertContextCount(0);\n"
        // Keep track of the number of responses.
        "var count = 0;\n"
        // Send the query.
        "var request_id = window.mrtQuery({\n"
        "  request: '" +
        std::string(kSingleQueryRequest) +
        "',\n"
        "  persistent: false,\n"
        "  onSuccess: function(response) {\n"
        "    window.mrtNotify('error-onSuccess');\n"
        "  },\n"
        "  onFailure: function(error_code, error_message) {\n"
        // Request should be removed before callback is executed.
        "    window.mrtAssertTotalCount(0);\n"
        "    window.mrtAssertBrowserCount(0);\n"
        "    window.mrtAssertContextCount(0);\n"
        "    if (error_code == -1 && "
        "error_message == 'The query has been canceled') {\n"
        "      window.mrtNotify('failure');\n"
        "    } else {\n"
        "      window.mrtNotify('error-onFailure');\n"
        "    }\n"
        "  }\n"
        "});\n"
        // Request should exist.
        "window.mrtAssertTotalCount(1);\n"
        "window.mrtAssertBrowserCount(1);\n"
        "window.mrtAssertContextCount(1);\n";

    html += "</script></body></html>";
    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_STREQ("failure", message.c_str());

    got_notify_.yes();

    DestroyTest();
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);
    EXPECT_NE(0, query_id);
    EXPECT_FALSE(persistent);
    EXPECT_STREQ(kSingleQueryRequest, request.ToString().c_str());

    got_on_query_.yes();

    return false;
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    EXPECT_FALSE(true);  // Not reached.
  }

  void DestroyTest() override {
    EXPECT_TRUE(got_on_query_);
    EXPECT_TRUE(got_notify_);

    TestHandler::DestroyTest();
  }

 private:
  TrackCallback got_on_query_;
  TrackCallback got_notify_;
};

}  // namespace

// Test that a single unhandled query results in a call to onFailure.
TEST(MessageRouterTest, SingleUnhandledQuery) {
  CefRefPtr<SingleUnhandledQueryTestHandler> handler =
      new SingleUnhandledQueryTestHandler();
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

const char kMultiQueryRequestId[] = "request_id";
const char kMultiQueryRepeatCt[] = "repeat_ct";
const char kMultiQueryRequest[] = "request";
const char kMultiQueryResponse[] = "response";
const char kMultiQuerySuccess[] = "success";
const char kMultiQueryError[] = "error";
const char kMultiQueryErrorMessage[] = "errormsg";
const int kMultiQueryPersistentResponseCount = 5;

// Generates HTML and verifies results for multiple simultanious queries.
class MultiQueryManager : public CefMessageRouterBrowserSide::Handler {
 public:
  enum TestType {
    // Initiates a non-persistent query with a successful response.
    // OnQuery and OnNotify will be called.
    SUCCESS,

    // Initiates a non-persistent query with a failure response.
    // OnQuery and OnNotify will be called.
    FAILURE,

    // Initiates a persistent query with multiple successful responses.
    // OnQuery, OnNotify and OnQueryCanceled will be called.
    PERSISTENT_SUCCESS,

    // Initiates a persistent query with multiple successful responses and one
    // failure response.
    // OnQuery and OnNotify will be called.
    PERSISTENT_FAILURE,

    // Initiates a non-persistent query that will be canceled via JavaScript.
    // No JavaScript callbacks will be executed.
    // OnQuery and OnQueryCanceled will be called.
    CANCEL,

    // Initiates a non-persistent query that will not be manually canceled.
    // No JavaScript callbacks will be executed.
    // OnQuery and OnQueryCanceled will be called.
    AUTOCANCEL,

    // Initiates a persistent query with multiple successful responses that will
    // not be manually canceled.
    // OnQuery, OnNotify and OnQueryCanceled will be called.
    PERSISTENT_AUTOCANCEL,
  };

  class Observer {
   public:
    // Called when all manual queries are complete.
    virtual void OnManualQueriesCompleted(MultiQueryManager* manager) {}

    // Called when all queries are complete.
    virtual void OnAllQueriesCompleted(MultiQueryManager* manager) {}

   protected:
    virtual ~Observer() {}
  };

  MultiQueryManager(const std::string& label,
                    bool synchronous,
                    int id_offset = 0)
      : label_(label),
        synchronous_(synchronous),
        id_offset_(id_offset),
        finalized_(false),
        running_(false),
        manual_total_(0),
        received_count_(0),
        manual_complete_count_(0),
        auto_complete_count_(0),
        will_cancel_by_removing_handler_(false),
        weak_ptr_factory_(this) {}

  virtual ~MultiQueryManager() {}

  std::string label() const { return label_; }

  void AddObserver(Observer* observer) {
    EXPECT_FALSE(running_);
    observer_set_.insert(observer);
  }

  void RemoveObserver(Observer* observer) {
    EXPECT_FALSE(running_);
    EXPECT_TRUE(observer_set_.erase(observer));
  }

  // Can be called from any thread, but should always be called from the same
  // thread.
  void AddTestQuery(TestType type) {
    EXPECT_FALSE(finalized_);
    test_query_vector_.push_back(TestQuery(type));
    if (!IsAuto(type))
      manual_total_++;
  }

  // Must be called after AddTestQuery and before the manager is used.
  void Finalize() {
    EXPECT_FALSE(finalized_);
    finalized_ = true;
  }

  // Call after all manual queries have completed if you intend to cancel auto
  // queries by removing the handler.
  void WillCancelByRemovingHandler() {
    EXPECT_TRUE(IsManualComplete());
    will_cancel_by_removing_handler_ = true;
  }

  std::string GetHTML(bool assert_total, bool assert_browser) const {
    EXPECT_TRUE(finalized_);
    EXPECT_FALSE(running_);

    std::string html;

    html = "<html><body>" + label_ + "<script>\n";

    // No requests should exist.
    if (assert_total)
      html += "window.mrtAssertTotalCount(0);\n";
    if (assert_browser)
      html += "window.mrtAssertBrowserCount(0);\n";
    html += "window.mrtAssertContextCount(0);\n";

    if (synchronous_) {
      // Run all of the queries synchronously. None will complete before the
      // last one begins.
      for (size_t i = 0; i < test_query_vector_.size(); ++i) {
        const TestQuery& query = test_query_vector_[i];
        html += GetQueryHTML(static_cast<int>(i), query);
      }

      const int total_ct = static_cast<int>(test_query_vector_.size());

      // Pending requests should match the total created.
      const std::string& total_val = GetIntString(total_ct);
      if (assert_total)
        html += "window.mrtAssertTotalCount(" + total_val + ");\n";
      if (assert_browser)
        html += "window.mrtAssertBrowserCount(" + total_val + ");\n";
      html += "window.mrtAssertContextCount(" + total_val + ");\n";

      int cancel_ct = 0;

      // Cancel all of the queries with type CANCEL.
      for (size_t i = 0; i < test_query_vector_.size(); ++i) {
        const TestQuery& query = test_query_vector_[i];
        if (query.type == CANCEL) {
          html += GetCancelHTML(static_cast<int>(i), query);
          cancel_ct++;
        }
      }

      if (cancel_ct > 0) {
        // Pending requests should match the total not canceled.
        const std::string& cancel_val = GetIntString(total_ct - cancel_ct);
        if (assert_total)
          html += "window.mrtAssertTotalCount(" + cancel_val + ");\n";
        if (assert_browser)
          html += "window.mrtAssertBrowserCount(" + cancel_val + ");\n";
        html += "window.mrtAssertContextCount(" + cancel_val + ");\n";
      }
    } else {
      // Run all of the queries asynchronously. Some may complete before
      // others begin.
      for (size_t i = 0; i < test_query_vector_.size(); ++i) {
        const TestQuery& query = test_query_vector_[i];

        const int index = static_cast<int>(i);

        // Each request is delayed by 10ms from the previous request.
        const std::string& delay_val = GetIntString(index);
        const std::string& query_html = GetQueryHTML(index, query);

        html += "window.setTimeout(function() {\n" + query_html;

        if (query.type == CANCEL) {
          // Cancel the query asynchronously with a 10ms delay.
          const std::string& request_id_var =
              GetIDString(kMultiQueryRequestId, index);
          html +=
              "  window.setTimeout(function() {\n"
              "    window.mrtQueryCancel(" +
              request_id_var +
              ");\n"
              "  }, 1);\n";
        }

        html += "\n}, " + delay_val + ");\n";
      }
    }

    html += "</script></body></html>";

    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) {
    EXPECT_TRUE(finalized_);
    EXPECT_UI_THREAD();

    if (!running_)
      running_ = true;

    EXPECT_TRUE(browser.get());
    EXPECT_TRUE(frame.get());

    std::string value;
    int index = 0;
    EXPECT_TRUE(SplitIDString(message, &value, &index));

    TestQuery& query = test_query_vector_[index];

    // Verify that browser and frame are the same.
    EXPECT_EQ(query.browser_id, browser->GetIdentifier()) << index;
    EXPECT_EQ(query.frame_id, frame->GetIdentifier()) << index;

    // Verify a successful/expected result.
    if (will_cancel_by_removing_handler_) {
      // Auto queries receive an onFailure callback which will notify with error
      // when the handler is removed.
      EXPECT_STREQ(kMultiQueryError, value.c_str()) << index;
      EXPECT_TRUE(IsAuto(query.type)) << index;
      EXPECT_TRUE(query.got_query) << index;
      if (query.type == PERSISTENT_AUTOCANCEL)
        EXPECT_TRUE(query.got_success) << index;
      else
        EXPECT_FALSE(query.got_success) << index;

      query.got_error.yes();

      // There's a race between OnQueryCanceled and OnNotification. Only call
      // OnQueryCompleted a single time.
      if (query.got_query_canceled)
        OnQueryCompleted(query.type);
    } else {
      EXPECT_STREQ(kMultiQuerySuccess, value.c_str()) << index;
      EXPECT_TRUE(WillNotify(query.type)) << index;
      EXPECT_TRUE(query.got_query) << index;
      EXPECT_FALSE(query.got_query_canceled) << index;
      EXPECT_FALSE(query.got_success) << index;

      query.got_success.yes();

      // PERSISTENT_AUTOCANCEL doesn't call OnReceiveCompleted from OnQuery.
      if (query.type == PERSISTENT_AUTOCANCEL)
        OnReceiveCompleted(query.type);

      // Call OnQueryCompleted for types that don't get OnQueryCanceled.
      if (!WillCancel(query.type))
        OnQueryCompleted(query.type);
    }
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    EXPECT_TRUE(finalized_);
    EXPECT_UI_THREAD();

    if (!running_)
      running_ = true;

    EXPECT_TRUE(browser.get());
    EXPECT_TRUE(frame.get());
    EXPECT_NE(0, query_id);

    std::string value;
    int index = 0;
    EXPECT_TRUE(SplitIDString(request, &value, &index));

    TestQuery& query = test_query_vector_[index];

    if (IsPersistent(query.type))
      EXPECT_TRUE(persistent);
    else
      EXPECT_FALSE(persistent);

    // Verify expected request.
    EXPECT_STREQ(kMultiQueryRequest, value.c_str()) << index;

    // Verify that call order is correct.
    EXPECT_FALSE(query.got_query) << index;
    EXPECT_FALSE(query.got_query_canceled) << index;
    EXPECT_FALSE(query.got_success) << index;
    EXPECT_FALSE(query.got_error) << index;

    query.got_query.yes();

    query.browser_id = browser->GetIdentifier();
    query.frame_id = frame->GetIdentifier();
    query.is_main_frame = frame->IsMain();

    if (query.type == SUCCESS) {
      // Send the single success response.
      callback->Success(GetIDString(kMultiQueryResponse, index));
    } else if (IsPersistent(query.type)) {
      // Send the required number of successful responses.
      const std::string& response = GetIDString(kMultiQueryResponse, index);
      for (int i = 0; i < kMultiQueryPersistentResponseCount; ++i)
        callback->Success(response);
    }

    if (WillFail(query.type)) {
      // Send the single failure response.
      callback->Failure(index, GetIDString(kMultiQueryErrorMessage, index));
    }

    if (WillCancel(query.type)) {
      // Hold onto the callback until the query is canceled.
      query.query_id = query_id;
      query.callback = callback;
    }

    // PERSISTENT_AUTOCANCEL will call OnReceiveCompleted once the success
    // notification is received.
    if (query.type != PERSISTENT_AUTOCANCEL)
      OnReceiveCompleted(query.type);

    return true;
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    EXPECT_TRUE(finalized_);
    EXPECT_UI_THREAD();

    if (!running_)
      running_ = true;

    EXPECT_TRUE(browser.get());
    EXPECT_TRUE(frame.get());
    EXPECT_NE(0, query_id);

    bool found = false;
    for (size_t i = 0; i < test_query_vector_.size(); ++i) {
      TestQuery& query = test_query_vector_[i];
      if (query.query_id == query_id) {
        // Verify that browser and frame are the same.
        EXPECT_EQ(query.browser_id, browser->GetIdentifier()) << i;
        if (query.is_main_frame) {
          EXPECT_TRUE(frame->IsMain()) << i;
        } else {
          EXPECT_FALSE(frame->IsMain()) << i;
          EXPECT_EQ(query.frame_id, frame->GetIdentifier()) << i;
        }

        // Verify a successful/expected result.
        EXPECT_TRUE(WillCancel(query.type)) << i;
        EXPECT_TRUE(query.callback.get()) << i;

        // Release the callback.
        query.callback = nullptr;

        // Verify that call order is correct.
        EXPECT_TRUE(query.got_query) << i;

        if (query.type == CANCEL || query.type == AUTOCANCEL) {
          // No JavaScript onSuccess callback executes.
          EXPECT_FALSE(query.got_success) << i;
        } else {
          // JavaScript onSuccess does execute before cancellation.
          EXPECT_TRUE(query.got_success) << i;
        }

        query.got_query_canceled.yes();

        if (will_cancel_by_removing_handler_) {
          // There's a race between OnQueryCanceled and OnNotification. Only
          // call OnQueryCompleted a single time.
          if (query.got_error)
            OnQueryCompleted(query.type);
        } else {
          EXPECT_FALSE(query.got_error) << i;

          // Cancellation is always completion.
          OnQueryCompleted(query.type);
        }

        found = true;
        break;
      }
    }
    EXPECT_TRUE(found);
  }

  // Asserts that all queries have completed.
  void AssertAllComplete() const {
    EXPECT_TRUE(finalized_);
    EXPECT_FALSE(running_);
    EXPECT_UI_THREAD();

    for (size_t i = 0; i < test_query_vector_.size(); ++i) {
      const TestQuery& query = test_query_vector_[i];
      EXPECT_TRUE(query.got_query) << i;

      if (WillCancel(query.type))
        EXPECT_TRUE(query.got_query_canceled) << i;
      else
        EXPECT_FALSE(query.got_query_canceled) << i;

      if (WillNotify(query.type))
        EXPECT_TRUE(query.got_success) << i;
      else
        EXPECT_FALSE(query.got_success) << i;

      if (IsAuto(query.type) && will_cancel_by_removing_handler_)
        EXPECT_TRUE(query.got_error);
      else
        EXPECT_FALSE(query.got_error);

      EXPECT_FALSE(query.callback.get()) << i;
    }
  }

  // Returns true if all manual queries have completed.
  bool IsManualComplete() const {
    EXPECT_TRUE(finalized_);
    EXPECT_UI_THREAD();

    return (manual_complete_count_ == manual_total_);
  }

  // Returns true if all queries have completed.
  bool IsAllComplete() const {
    EXPECT_TRUE(finalized_);
    EXPECT_UI_THREAD();

    return (manual_complete_count_ + auto_complete_count_ ==
            static_cast<int>(test_query_vector_.size()));
  }

  bool HasAutoQueries() const {
    return (manual_total_ != static_cast<int>(test_query_vector_.size()));
  }

 private:
  struct TestQuery {
    explicit TestQuery(TestType test_type)
        : type(test_type),
          browser_id(0),
          frame_id(0),
          is_main_frame(false),
          query_id(0) {}

    TestType type;

    // Set in OnQuery and verified in OnNotify or OnQueryCanceled.
    int browser_id;
    int64 frame_id;
    bool is_main_frame;

    // Used when a query is canceled.
    int64 query_id;
    CefRefPtr<Callback> callback;

    TrackCallback got_query;
    TrackCallback got_query_canceled;
    TrackCallback got_success;
    TrackCallback got_error;
  };

  class NotifyTask : public CefTask {
   public:
    NotifyTask(base::WeakPtr<MultiQueryManager> weak_ptr, bool notify_all)
        : weak_ptr_(weak_ptr), notify_all_(notify_all) {}

    void Execute() override {
      if (weak_ptr_) {
        if (notify_all_)
          weak_ptr_->NotifyAllQueriesCompleted();
        else
          weak_ptr_->NotifyManualQueriesCompleted();
      }
    }

   private:
    base::WeakPtr<MultiQueryManager> weak_ptr_;
    const bool notify_all_;

    IMPLEMENT_REFCOUNTING(NotifyTask);
  };

  static bool IsAuto(TestType type) {
    return (type == AUTOCANCEL || type == PERSISTENT_AUTOCANCEL);
  }

  static bool IsPersistent(TestType type) {
    return (type == PERSISTENT_SUCCESS || type == PERSISTENT_FAILURE ||
            type == PERSISTENT_AUTOCANCEL);
  }

  static bool WillFail(TestType type) {
    return (type == FAILURE || type == PERSISTENT_FAILURE);
  }

  static bool WillCancel(TestType type) {
    return (type == PERSISTENT_SUCCESS || type == CANCEL ||
            type == AUTOCANCEL || type == PERSISTENT_AUTOCANCEL);
  }

  static bool WillNotify(TestType type) {
    return (type == SUCCESS || type == PERSISTENT_SUCCESS || type == FAILURE ||
            type == PERSISTENT_FAILURE || type == PERSISTENT_AUTOCANCEL);
  }

  void OnReceiveCompleted(TestType type) {
    const int total_count = static_cast<int>(test_query_vector_.size());
    if (++received_count_ == total_count && manual_total_ == 0) {
      // There aren't any manual queries so notify here.
      CefPostTask(TID_UI,
                  new NotifyTask(weak_ptr_factory_.GetWeakPtr(), false));
    }
  }

  void OnQueryCompleted(TestType type) {
    const int total_count = static_cast<int>(test_query_vector_.size());
    EXPECT_LT(manual_complete_count_ + auto_complete_count_, total_count);
    EXPECT_LE(manual_complete_count_, manual_total_);

    const bool is_auto = IsAuto(type);
    if (is_auto)
      auto_complete_count_++;
    else if (++manual_complete_count_ == manual_total_) {
      CefPostTask(TID_UI,
                  new NotifyTask(weak_ptr_factory_.GetWeakPtr(), false));
    }

    if (auto_complete_count_ + manual_complete_count_ == total_count) {
      running_ = false;
      CefPostTask(TID_UI, new NotifyTask(weak_ptr_factory_.GetWeakPtr(), true));
    }
  }

  void NotifyManualQueriesCompleted() {
    if (observer_set_.empty())
      return;

    // Use a copy of the set in case an Observer is removed while we're
    // iterating.
    ObserverSet observer_set = observer_set_;

    ObserverSet::const_iterator it = observer_set.begin();
    for (; it != observer_set.end(); ++it) {
      (*it)->OnManualQueriesCompleted(this);
    }
  }

  void NotifyAllQueriesCompleted() {
    if (observer_set_.empty())
      return;

    // Use a copy of the set in case an Observer is removed while we're
    // iterating.
    ObserverSet observer_set = observer_set_;

    ObserverSet::const_iterator it = observer_set.begin();
    for (; it != observer_set.end(); ++it) {
      (*it)->OnAllQueriesCompleted(this);
    }
  }

  std::string GetQueryHTML(const int index, const TestQuery& query) const {
    const std::string& request_id_var =
        GetIDString(kMultiQueryRequestId, index);
    const std::string& repeat_ct_var = GetIDString(kMultiQueryRepeatCt, index);
    const std::string& request_val =
        GetIDString(std::string(kMultiQueryRequest) + ":", index);
    const std::string& success_val =
        GetIDString(std::string(kMultiQuerySuccess) + ":", index);
    const std::string& error_val =
        GetIDString(std::string(kMultiQueryError) + ":", index);

    std::string html;

    const bool persistent = IsPersistent(query.type);

    if (persistent)
      html += "var " + repeat_ct_var + " = 0;\n";

    html += "var " + request_id_var +
            " = window.mrtQuery({\n"
            "  request: '" +
            request_val +
            "',\n"
            "  persistent: " +
            (persistent ? "true" : "false") + ",\n";

    if (query.type == SUCCESS) {
      const std::string& response_val = GetIDString(kMultiQueryResponse, index);

      html +=
          "  onSuccess: function(response) {\n"
          "    if (response == '" +
          response_val +
          "')\n"
          "      window.mrtNotify('" +
          success_val +
          "');\n"
          "    else\n"
          "      window.mrtNotify('" +
          error_val +
          "');\n"
          "  },\n"
          "  onFailure: function(error_code, error_message) {\n"
          "    window.mrtNotify('" +
          error_val +
          "');\n"
          "  }\n";
    } else if (query.type == FAILURE) {
      const std::string& error_code_val = GetIntString(index);
      const std::string& error_message_val =
          GetIDString(kMultiQueryErrorMessage, index);

      html +=
          "  onSuccess: function(response) {\n"
          "    window.mrtNotify('" +
          error_val +
          "');\n"
          "  },\n"
          "  onFailure: function(error_code, error_message) {\n"
          "    if (error_code == " +
          error_code_val + " && error_message == '" + error_message_val +
          "')\n"
          "      window.mrtNotify('" +
          success_val +
          "');\n"
          "    else\n"
          "      window.mrtNotify('" +
          error_val +
          "');\n"
          "  }\n";
    } else if (query.type == PERSISTENT_SUCCESS ||
               query.type == PERSISTENT_AUTOCANCEL) {
      const std::string& response_val = GetIDString(kMultiQueryResponse, index);
      const std::string& repeat_ct =
          GetIntString(kMultiQueryPersistentResponseCount);

      html +=
          "  onSuccess: function(response) {\n"
          "    if (response == '" +
          response_val +
          "') {\n"
          // Should get repeat_ct number of successful responses.
          "      if (++" +
          repeat_ct_var + " == " + repeat_ct +
          ") {\n"
          "        window.mrtNotify('" +
          success_val + "');\n";

      if (query.type == PERSISTENT_SUCCESS) {
        // Manually cancel the request.
        html += "        window.mrtQueryCancel(" + request_id_var + ");\n";
      }

      html +=
          "      }\n"
          "    } else {\n"
          "      window.mrtNotify('" +
          error_val +
          "');\n"
          "    }\n"
          "  },\n"
          "  onFailure: function(error_code, error_message) {\n"
          "    window.mrtNotify('" +
          error_val +
          "');\n"
          "  }\n";
    } else if (query.type == PERSISTENT_FAILURE) {
      const std::string& error_code_val = GetIntString(index);
      const std::string& error_message_val =
          GetIDString(kMultiQueryErrorMessage, index);
      const std::string& repeat_ct =
          GetIntString(kMultiQueryPersistentResponseCount);

      html +=
          "  onSuccess: function(response) {\n"
          // Should get some successful responses before failure.
          "    if (++" +
          repeat_ct_var + " > " + repeat_ct +
          ") {\n"
          "      window.mrtNotify('" +
          error_val +
          "');\n"
          "    }\n"
          "  },\n"
          "  onFailure: function(error_code, error_message) {\n"
          "    if (error_code == " +
          error_code_val + " && error_message == '" + error_message_val +
          "'"
          " && " +
          repeat_ct_var + " == " + repeat_ct +
          ")\n"
          "      window.mrtNotify('" +
          success_val +
          "');\n"
          "    else\n"
          "      window.mrtNotify('" +
          error_val +
          "');\n"
          "  }\n";
    } else if (query.type == CANCEL || query.type == AUTOCANCEL) {
      html +=
          "  onSuccess: function(response) {\n"
          "    window.mrtNotify('" +
          error_val +
          "');\n"
          "  },\n"
          "  onFailure: function(error_code, error_message) {\n"
          "    window.mrtNotify('" +
          error_val +
          "');\n"
          "  }\n";
    }

    html += "});\n";

    return html;
  }

  std::string GetCancelHTML(const int index, const TestQuery& query) const {
    const std::string& request_id_var =
        GetIDString(kMultiQueryRequestId, index);
    return "window.mrtQueryCancel(" + request_id_var + ");\n";
  }

  std::string GetIDString(const std::string& prefix, int index) const {
    EXPECT_TRUE(!prefix.empty());
    std::stringstream ss;
    ss << prefix << GetIDFromIndex(index);
    return ss.str();
  }

  bool SplitIDString(const std::string& str,
                     std::string* value,
                     int* index) const {
    size_t pos = str.find(':');
    if (pos != std::string::npos) {
      *value = str.substr(0, pos);
      *index = GetIndexFromID(atoi(str.substr(pos + 1).c_str()));
      return (*index >= 0 &&
              *index < static_cast<int>(test_query_vector_.size()));
    }

    return false;
  }

  std::string GetIntString(int val) const {
    std::stringstream ss;
    ss << val;
    return ss.str();
  }

  int GetIDFromIndex(int index) const { return id_offset_ + index; }
  int GetIndexFromID(int id) const { return id - id_offset_; }

  const std::string label_;
  const bool synchronous_;
  const int id_offset_;

  typedef std::vector<TestQuery> TestQueryVector;
  TestQueryVector test_query_vector_;

  typedef std::set<Observer*> ObserverSet;
  ObserverSet observer_set_;

  // Set to true after all queries have been added.
  bool finalized_;
  // Set to true while queries are pending.
  bool running_;

  // Total number of queries that will manually complete.
  int manual_total_;

  // Number of queries that have been received.
  int received_count_;

  // Number of queries that have completed successfully.
  int manual_complete_count_;
  int auto_complete_count_;

  // If true any pending queries will receive an onFailure callback in addition
  // to be canceled.
  bool will_cancel_by_removing_handler_;

  // Should always be the last member.
  base::WeakPtrFactory<MultiQueryManager> weak_ptr_factory_;
};

void MakeTestQueries(MultiQueryManager* manager,
                     bool some,
                     int many_count = 200) {
  if (some) {
    // Test some queries of arbitrary types.
    // Use a hard-coded list so the behavior is deterministic across test runs.
    MultiQueryManager::TestType types[] = {
        MultiQueryManager::PERSISTENT_AUTOCANCEL,
        MultiQueryManager::SUCCESS,
        MultiQueryManager::AUTOCANCEL,
        MultiQueryManager::PERSISTENT_FAILURE,
        MultiQueryManager::CANCEL,
        MultiQueryManager::FAILURE,
        MultiQueryManager::AUTOCANCEL,
        MultiQueryManager::SUCCESS,
        MultiQueryManager::PERSISTENT_SUCCESS,
        MultiQueryManager::SUCCESS,
        MultiQueryManager::PERSISTENT_AUTOCANCEL,
        MultiQueryManager::CANCEL,
        MultiQueryManager::PERSISTENT_SUCCESS,
        MultiQueryManager::FAILURE,
    };
    for (size_t i = 0; i < sizeof(types) / sizeof(types[0]); ++i) {
      manager->AddTestQuery(types[i]);
    }
  } else {
    // Test every type of query.
    for (int i = 0; i < many_count; ++i) {
      MultiQueryManager::TestType type = MultiQueryManager::SUCCESS;
      switch (i % 7) {
        case 0:
          type = MultiQueryManager::SUCCESS;
          break;
        case 1:
          type = MultiQueryManager::FAILURE;
          break;
        case 2:
          type = MultiQueryManager::PERSISTENT_SUCCESS;
          break;
        case 3:
          type = MultiQueryManager::PERSISTENT_FAILURE;
          break;
        case 4:
          type = MultiQueryManager::CANCEL;
          break;
        case 5:
          type = MultiQueryManager::AUTOCANCEL;
          break;
        case 6:
          type = MultiQueryManager::PERSISTENT_AUTOCANCEL;
          break;
      }
      manager->AddTestQuery(type);
    }
  }
  manager->Finalize();
}

// Test multiple queries in a single page load with a single frame.
class MultiQuerySingleFrameTestHandler : public SingleLoadTestHandler,
                                         public MultiQueryManager::Observer {
 public:
  enum CancelType {
    CANCEL_BY_NAVIGATION,
    CANCEL_BY_REMOVING_HANDLER,
    CANCEL_BY_CLOSING_BROWSER,
  };

  MultiQuerySingleFrameTestHandler(
      bool synchronous,
      CancelType cancel_type = CANCEL_BY_NAVIGATION)
      : manager_(std::string(), synchronous), cancel_type_(cancel_type) {
    manager_.AddObserver(this);
  }

  std::string GetMainHTML() override { return manager_.GetHTML(true, true); }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    manager_.OnNotify(browser, frame, message);
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    return manager_.OnQuery(browser, frame, query_id, request, persistent,
                            callback);
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    manager_.OnQueryCanceled(browser, frame, query_id);
  }

  void OnManualQueriesCompleted(MultiQueryManager* manager) override {
    EXPECT_EQ(manager, &manager_);
    if (manager_.HasAutoQueries()) {
      if (cancel_type_ == CANCEL_BY_NAVIGATION) {
        // Navigate somewhere else to terminate the auto queries.
        GetBrowser()->GetMainFrame()->LoadURL(std::string(kTestDomain1) +
                                              "cancel.html");
      } else if (cancel_type_ == CANCEL_BY_REMOVING_HANDLER) {
        // Change the expected behavior in the manager.
        manager_.WillCancelByRemovingHandler();
        GetRouter()->RemoveHandler(this);
        // All queries should be immediately canceled.
        AssertQueryCount(nullptr, nullptr, 0);
      } else if (cancel_type_ == CANCEL_BY_CLOSING_BROWSER) {
        // Change the expected behavior in the handler.
        SetSignalCompletionWhenAllBrowsersClose(false);
        CloseBrowser(GetBrowser(), false);
      }
    }
  }

  void OnAllQueriesCompleted(MultiQueryManager* manager) override {
    EXPECT_EQ(manager, &manager_);

    // All queries should be canceled.
    AssertQueryCount(nullptr, nullptr, 0);

    DestroyTest();

    if (!SignalCompletionWhenAllBrowsersClose()) {
      // Complete asynchronously so the call stack has a chance to unwind.
      CefPostTask(
          TID_UI,
          base::Bind(&MultiQuerySingleFrameTestHandler::TestComplete, this));
    }
  }

  void DestroyTest() override {
    manager_.AssertAllComplete();
    TestHandler::DestroyTest();
  }

  MultiQueryManager* GetManager() { return &manager_; }

 private:
  MultiQueryManager manager_;
  const CancelType cancel_type_;
};

}  // namespace

#define MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(name, type, synchronous) \
  TEST(MessageRouterTest, name) {                                   \
    CefRefPtr<MultiQuerySingleFrameTestHandler> handler =           \
        new MultiQuerySingleFrameTestHandler(synchronous);          \
    MultiQueryManager* manager = handler->GetManager();             \
    manager->AddTestQuery(MultiQueryManager::type);                 \
    manager->Finalize();                                            \
    handler->ExecuteTest();                                         \
    ReleaseAndWaitForDestructor(handler);                           \
  }

// Test the query types individually.
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncSuccess,
                                   SUCCESS,
                                   true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncSuccess,
                                   SUCCESS,
                                   false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncFailure,
                                   FAILURE,
                                   true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncFailure,
                                   FAILURE,
                                   false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncPersistentSuccess,
                                   PERSISTENT_SUCCESS,
                                   true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncPersistentSuccess,
                                   PERSISTENT_SUCCESS,
                                   false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameSyncPersistentFailure,
                                   PERSISTENT_FAILURE,
                                   true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAsyncPersistentFailure,
                                   PERSISTENT_FAILURE,
                                   false)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameCancel, CANCEL, true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFrameAutoCancel,
                                   AUTOCANCEL,
                                   true)
MULTI_QUERY_SINGLE_FRAME_TYPE_TEST(MultiQuerySingleFramePersistentAutoCancel,
                                   PERSISTENT_AUTOCANCEL,
                                   true)

// Test that one frame can run some queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameSyncSome) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(true);
  MakeTestQueries(handler->GetManager(), true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that one frame can run some queries successfully in an asynchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameAsyncSome) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(false);
  MakeTestQueries(handler->GetManager(), true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that one frame can run many queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameSyncMany) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(true);
  MakeTestQueries(handler->GetManager(), false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that one frame can run many queries successfully in an asynchronous
// manner.
TEST(MessageRouterTest, MultiQuerySingleFrameAsyncMany) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(false);
  MakeTestQueries(handler->GetManager(), false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that pending queries can be canceled by removing the handler.
TEST(MessageRouterTest, MultiQuerySingleFrameCancelByRemovingHandler) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(
          false, MultiQuerySingleFrameTestHandler::CANCEL_BY_REMOVING_HANDLER);
  MakeTestQueries(handler->GetManager(), false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that pending queries can be canceled by closing the browser.
TEST(MessageRouterTest, MultiQuerySingleFrameCancelByClosingBrowser) {
  CefRefPtr<MultiQuerySingleFrameTestHandler> handler =
      new MultiQuerySingleFrameTestHandler(
          false, MultiQuerySingleFrameTestHandler::CANCEL_BY_CLOSING_BROWSER);
  MakeTestQueries(handler->GetManager(), false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

// Test multiple handlers.
class MultiQueryMultiHandlerTestHandler : public SingleLoadTestHandler,
                                          public MultiQueryManager::Observer {
 public:
  class Handler : public CefMessageRouterBrowserSide::Handler {
   public:
    Handler(MultiQueryMultiHandlerTestHandler* test_handler, int index)
        : test_handler_(test_handler), index_(index), query_id_(0) {}

    bool OnQuery(CefRefPtr<CefBrowser> browser,
                 CefRefPtr<CefFrame> frame,
                 int64 query_id,
                 const CefString& request,
                 bool persistent,
                 CefRefPtr<Callback> callback) override {
      // Each handler only handles a single request.
      std::stringstream ss;
      ss << kMultiQueryRequest << ":" << index_;
      const std::string& handled_request = ss.str();
      if (request != handled_request)
        return false;

      // Verify that handlers are called in the correct order.
      if (index_ == 0) {
        EXPECT_FALSE(test_handler_->got_query0_);
        EXPECT_FALSE(test_handler_->got_query1_);
        EXPECT_FALSE(test_handler_->got_query2_);

        test_handler_->got_query0_.yes();
      } else if (index_ == 1) {
        EXPECT_TRUE(test_handler_->got_query0_);
        EXPECT_FALSE(test_handler_->got_query1_);
        EXPECT_FALSE(test_handler_->got_query2_);

        test_handler_->got_query1_.yes();
      } else if (index_ == 2) {
        EXPECT_TRUE(test_handler_->got_query0_);
        EXPECT_TRUE(test_handler_->got_query1_);
        EXPECT_FALSE(test_handler_->got_query2_);

        test_handler_->got_query2_.yes();
      }

      query_id_ = query_id;
      return test_handler_->OnQuery(browser, frame, query_id, request,
                                    persistent, callback);
    }

    void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                         CefRefPtr<CefFrame> frame,
                         int64 query_id) override {
      // Verify that the correct handler is called for cancellation.
      EXPECT_EQ(query_id_, query_id);

      if (index_ == 0) {
        EXPECT_FALSE(test_handler_->got_query_canceled0_);
        test_handler_->got_query_canceled0_.yes();
      } else if (index_ == 1) {
        EXPECT_FALSE(test_handler_->got_query_canceled1_);
        test_handler_->got_query_canceled1_.yes();
      } else if (index_ == 2) {
        EXPECT_FALSE(test_handler_->got_query_canceled2_);
        test_handler_->got_query_canceled2_.yes();
      }

      test_handler_->OnQueryCanceled(browser, frame, query_id);
    }

   private:
    MultiQueryMultiHandlerTestHandler* test_handler_;
    const int index_;
    int query_id_;
  };

  MultiQueryMultiHandlerTestHandler(bool synchronous,
                                    bool cancel_by_removing_handler)
      : manager_(std::string(), synchronous, 0),
        handler0_(this, 0),
        handler1_(this, 1),
        handler2_(this, 2),
        cancel_by_removing_handler_(cancel_by_removing_handler) {
    manager_.AddObserver(this);

    // Each handler will handle one of the queries.
    manager_.AddTestQuery(MultiQueryManager::PERSISTENT_AUTOCANCEL);
    manager_.AddTestQuery(MultiQueryManager::PERSISTENT_AUTOCANCEL);
    manager_.AddTestQuery(MultiQueryManager::PERSISTENT_AUTOCANCEL);
    manager_.Finalize();
  }

  std::string GetMainHTML() override { return manager_.GetHTML(true, true); }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    manager_.OnNotify(browser, frame, message);
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    return manager_.OnQuery(browser, frame, query_id, request, persistent,
                            callback);
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    AssertMainBrowser(browser);
    AssertMainFrame(frame);

    manager_.OnQueryCanceled(browser, frame, query_id);
  }

  void OnManualQueriesCompleted(MultiQueryManager* manager) override {
    EXPECT_EQ(manager, &manager_);

    EXPECT_TRUE(got_query0_);
    EXPECT_TRUE(got_query1_);
    EXPECT_TRUE(got_query2_);
    EXPECT_FALSE(got_query_canceled0_);
    EXPECT_FALSE(got_query_canceled1_);
    EXPECT_FALSE(got_query_canceled2_);

    EXPECT_TRUE(manager_.HasAutoQueries());

    CefRefPtr<CefMessageRouterBrowserSide> router = GetRouter();

    // Remove one handler to cancel a query.

    if (cancel_by_removing_handler_) {
      manager_.WillCancelByRemovingHandler();

      // Each query should be canceled as the handler is removed.
      EXPECT_TRUE(router->RemoveHandler(&handler1_));
      EXPECT_FALSE(got_query_canceled0_);
      EXPECT_TRUE(got_query_canceled1_);
      EXPECT_FALSE(got_query_canceled2_);

      EXPECT_TRUE(router->RemoveHandler(&handler2_));
      EXPECT_FALSE(got_query_canceled0_);
      EXPECT_TRUE(got_query_canceled2_);

      EXPECT_TRUE(router->RemoveHandler(&handler0_));
      EXPECT_TRUE(got_query_canceled0_);
    } else {
      GetBrowser()->GetMainFrame()->LoadURL(std::string(kTestDomain1) +
                                            "cancel.html");
    }
  }

  void OnAllQueriesCompleted(MultiQueryManager* manager) override {
    EXPECT_EQ(manager, &manager_);

    // All queries should be canceled.
    AssertQueryCount(nullptr, nullptr, 0);

    DestroyTest();
  }

  void DestroyTest() override {
    EXPECT_TRUE(got_query0_);
    EXPECT_TRUE(got_query1_);
    EXPECT_TRUE(got_query2_);
    EXPECT_TRUE(got_query_canceled0_);
    EXPECT_TRUE(got_query_canceled1_);
    EXPECT_TRUE(got_query_canceled2_);

    manager_.AssertAllComplete();
    TestHandler::DestroyTest();
  }

 protected:
  void AddHandlers(
      CefRefPtr<CefMessageRouterBrowserSide> message_router) override {
    // OnQuery call order will verify that the first/last ordering works as
    // expected.
    EXPECT_TRUE(message_router->AddHandler(&handler1_, true));
    EXPECT_TRUE(message_router->AddHandler(&handler0_, true));
    EXPECT_TRUE(message_router->AddHandler(&handler2_, false));

    // Can't add the same handler multiple times.
    EXPECT_FALSE(message_router->AddHandler(&handler1_, true));
  }

 private:
  MultiQueryManager manager_;
  Handler handler0_;
  Handler handler1_;
  Handler handler2_;

  const bool cancel_by_removing_handler_;

  TrackCallback got_query0_;
  TrackCallback got_query1_;
  TrackCallback got_query2_;

  TrackCallback got_query_canceled0_;
  TrackCallback got_query_canceled1_;
  TrackCallback got_query_canceled2_;
};

}  // namespace

// Test that multiple handlers behave correctly.
TEST(MessageRouterTest, MultiQueryMultiHandler) {
  CefRefPtr<MultiQueryMultiHandlerTestHandler> handler =
      new MultiQueryMultiHandlerTestHandler(false, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple handlers behave correctly. Cancel by removing the
// handlers.
TEST(MessageRouterTest, MultiQueryMultiHandlerCancelByRemovingHandler) {
  CefRefPtr<MultiQueryMultiHandlerTestHandler> handler =
      new MultiQueryMultiHandlerTestHandler(false, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

// Map of managers on a per-URL basis.
class MultiQueryManagerMap : public CefMessageRouterBrowserSide::Handler,
                             public MultiQueryManager::Observer {
 public:
  class Observer {
   public:
    // Called when all manual queries are complete.
    virtual void OnMapManualQueriesCompleted(MultiQueryManagerMap* map) {}

    // Called when all queries are complete.
    virtual void OnMapAllQueriesCompleted(MultiQueryManagerMap* map) {}

   protected:
    virtual ~Observer() {}
  };

  MultiQueryManagerMap()
      : finalized_(false),
        running_(false),
        manual_complete_count_(0),
        total_complete_count_(0) {}

  virtual ~MultiQueryManagerMap() { RemoveAllManagers(); }

  void AddObserver(Observer* observer) {
    EXPECT_FALSE(running_);
    observer_set_.insert(observer);
  }

  void RemoveObserver(Observer* observer) {
    EXPECT_FALSE(running_);
    EXPECT_TRUE(observer_set_.erase(observer));
  }

  MultiQueryManager* CreateManager(const std::string& url, bool synchronous) {
    EXPECT_FALSE(finalized_);

    MultiQueryManager* manager = new MultiQueryManager(
        url, synchronous, static_cast<int>(manager_map_.size()) * 1000);
    manager->AddObserver(this);
    all_managers_.push_back(manager);
    pending_managers_.push_back(manager);

    return manager;
  }

  void Finalize() {
    EXPECT_FALSE(finalized_);
    finalized_ = true;
  }

  std::string GetMainHTML() const {
    EXPECT_TRUE(finalized_);
    EXPECT_FALSE(running_);

    std::string html = "<html><body>\n";

    for (size_t i = 0; i < all_managers_.size(); ++i) {
      const std::string& url = all_managers_[i]->label();
      const std::string& name = GetNameForURL(url);
      html += "<iframe id=\"" + name + "\" src=\"" + url + "\"></iframe>\n";
    }

    html += "</body></html>";
    return html;
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) {
    EXPECT_TRUE(finalized_);
    if (!running_)
      running_ = true;

    MultiQueryManager* manager = GetManager(browser, frame);
    manager->OnNotify(browser, frame, message);
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    EXPECT_TRUE(finalized_);
    if (!running_)
      running_ = true;

    MultiQueryManager* manager = GetManager(browser, frame);
    return manager->OnQuery(browser, frame, query_id, request, persistent,
                            callback);
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    EXPECT_TRUE(finalized_);
    if (!running_)
      running_ = true;

    MultiQueryManager* manager = GetManager(browser, frame);
    manager->OnQueryCanceled(browser, frame, query_id);
  }

  void OnManualQueriesCompleted(MultiQueryManager* manager) override {
    const int size = static_cast<int>(all_managers_.size());
    EXPECT_LT(manual_complete_count_, size);
    if (++manual_complete_count_ == size) {
      running_ = false;

      // Notify observers.
      if (!observer_set_.empty()) {
        // Use a copy of the set in case an Observer is removed while we're
        // iterating.
        ObserverSet observer_set = observer_set_;

        ObserverSet::const_iterator it = observer_set.begin();
        for (; it != observer_set.end(); ++it) {
          (*it)->OnMapManualQueriesCompleted(this);
        }
      }
    }
  }

  void OnAllQueriesCompleted(MultiQueryManager* manager) override {
    const int size = static_cast<int>(all_managers_.size());
    EXPECT_LT(total_complete_count_, size);
    if (++total_complete_count_ == size) {
      running_ = false;

      // Notify observers.
      if (!observer_set_.empty()) {
        // Use a copy of the set in case an Observer is removed while we're
        // iterating.
        ObserverSet observer_set = observer_set_;

        ObserverSet::const_iterator it = observer_set.begin();
        for (; it != observer_set.end(); ++it) {
          (*it)->OnMapAllQueriesCompleted(this);
        }
      }
    }
  }

  bool AllComplete() const {
    EXPECT_TRUE(finalized_);

    for (size_t i = 0; i < all_managers_.size(); ++i) {
      if (!all_managers_[i]->IsAllComplete())
        return false;
    }
    return true;
  }

  void AssertAllComplete() const {
    EXPECT_TRUE(finalized_);
    EXPECT_TRUE(pending_managers_.empty());
    EXPECT_FALSE(running_);

    for (size_t i = 0; i < all_managers_.size(); ++i) {
      all_managers_[i]->AssertAllComplete();
    }
  }

  bool HasAutoQueries() const {
    for (size_t i = 0; i < all_managers_.size(); ++i) {
      if (all_managers_[i]->HasAutoQueries())
        return true;
    }

    return false;
  }

  void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame) {
    if (pending_managers_.empty())
      return;

    const std::string& expected_url = frame->GetURL();
    MultiQueryManager* next_manager = nullptr;

    // Find the pending manager that matches the expected URL.
    ManagerList::iterator it = pending_managers_.begin();
    for (; it != pending_managers_.end(); ++it) {
      if ((*it)->label() == expected_url) {
        next_manager = *it;
        pending_managers_.erase(it);
        break;
      }
    }

    EXPECT_TRUE(next_manager);

    const int browser_id = browser->GetIdentifier();
    // Always use the same ID for the main frame.
    const int64 frame_id = frame->IsMain() ? -1 : frame->GetIdentifier();

    const std::pair<int, int64>& id = std::make_pair(browser_id, frame_id);

    // Remove the currently active manager, if any.
    ManagerMap::iterator it2 = manager_map_.find(id);
    if (it2 != manager_map_.end())
      manager_map_.erase(it2);

    // Add the next manager to the active map.
    manager_map_.insert(std::make_pair(id, next_manager));
  }

  MultiQueryManager* GetManager(CefRefPtr<CefBrowser> browser,
                                CefRefPtr<CefFrame> frame) const {
    const int browser_id = browser->GetIdentifier();
    // Always use the same ID for the main frame.
    const int64 frame_id = frame->IsMain() ? -1 : frame->GetIdentifier();

    // Find the manager in the active map.
    ManagerMap::const_iterator it =
        manager_map_.find(std::make_pair(browser_id, frame_id));
    EXPECT_NE(it, manager_map_.end())
        << "browser_id = " << browser_id << ", frame_id = " << frame_id;
    return it->second;
  }

  void RemoveAllManagers() {
    EXPECT_TRUE(pending_managers_.empty());
    if (all_managers_.empty())
      return;

    for (size_t i = 0; i < all_managers_.size(); ++i) {
      delete all_managers_[i];
    }
    all_managers_.clear();
    manager_map_.clear();
  }

  static std::string GetNameForURL(const std::string& url) {
    // Extract the file name without extension.
    int pos1 = static_cast<int>(url.rfind("/"));
    int pos2 = static_cast<int>(url.rfind("."));
    EXPECT_TRUE(pos1 >= 0 && pos2 >= 0 && pos1 < pos2);
    return url.substr(pos1 + 1, pos2 - pos1 - 1);
  }

 private:
  typedef std::vector<MultiQueryManager*> ManagerList;
  // Map of (browser ID, frame ID) to manager.
  typedef std::map<std::pair<int, int64>, MultiQueryManager*> ManagerMap;

  // All managers that have been created.
  ManagerList all_managers_;
  // Managers that have not yet associated with a frame.
  ManagerList pending_managers_;
  // Managers that are currently active.
  ManagerMap manager_map_;

  typedef std::set<Observer*> ObserverSet;
  ObserverSet observer_set_;

  // Set to true after all query managers have been added.
  bool finalized_;
  // Set to true while queries are pending.
  bool running_;

  // Number of managers that have completed.
  int manual_complete_count_;
  int total_complete_count_;
};

// Test multiple queries in a single page load with multiple frames.
class MultiQueryMultiFrameTestHandler : public SingleLoadTestHandler,
                                        public MultiQueryManagerMap::Observer {
 public:
  MultiQueryMultiFrameTestHandler(bool synchronous, bool cancel_with_subnav)
      : synchronous_(synchronous), cancel_with_subnav_(cancel_with_subnav) {
    manager_map_.AddObserver(this);
  }

  void AddOtherResources() override {
    AddSubFrameResource("sub1");
    AddSubFrameResource("sub2");
    AddSubFrameResource("sub3");
    manager_map_.Finalize();

    if (manager_map_.HasAutoQueries()) {
      cancel_url_ = std::string(kTestDomain1) + "cancel.html";
      AddResource(cancel_url_, "<html><body>cancel</body></html>", "text/html");
    }
  }

  std::string GetMainHTML() override { return manager_map_.GetMainHTML(); }

  void OnLoadStart(CefRefPtr<CefBrowser> browser,
                   CefRefPtr<CefFrame> frame,
                   TransitionType transition_type) override {
    AssertMainBrowser(browser);
    if (!frame->IsMain())
      manager_map_.OnLoadStart(browser, frame);
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    AssertMainBrowser(browser);
    EXPECT_FALSE(frame->IsMain());

    manager_map_.OnNotify(browser, frame, message);
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    AssertMainBrowser(browser);
    EXPECT_FALSE(frame->IsMain());

    return manager_map_.OnQuery(browser, frame, query_id, request, persistent,
                                callback);
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    AssertMainBrowser(browser);
    EXPECT_FALSE(frame->IsMain());

    manager_map_.OnQueryCanceled(browser, frame, query_id);
  }

  void OnMapManualQueriesCompleted(MultiQueryManagerMap* map) override {
    EXPECT_EQ(map, &manager_map_);
    if (manager_map_.HasAutoQueries()) {
      CefRefPtr<CefFrame> frame = GetBrowser()->GetMainFrame();

      // Navigate somewhere else to terminate the auto queries.
      if (cancel_with_subnav_) {
        // Navigate each subframe individually.
        const std::string js = "document.getElementById('sub1').src = '" +
                               cancel_url_ +
                               "';"
                               "document.getElementById('sub2').src = '" +
                               cancel_url_ +
                               "';"
                               "document.getElementById('sub3').src = '" +
                               cancel_url_ + "';";

        frame->ExecuteJavaScript(js, frame->GetURL(), 0);
      } else {
        // Navigate the main frame.
        frame->LoadURL(cancel_url_);
      }
    }
  }

  void OnMapAllQueriesCompleted(MultiQueryManagerMap* map) override {
    EXPECT_EQ(map, &manager_map_);
    DestroyTest();
  }

  void DestroyTest() override {
    manager_map_.AssertAllComplete();
    TestHandler::DestroyTest();
  }

 private:
  void AddSubFrameResource(const std::string& name) {
    const std::string& url = std::string(kTestDomain1) + name + ".html";

    MultiQueryManager* manager = manager_map_.CreateManager(url, synchronous_);
    MakeTestQueries(manager, false, 100);

    const std::string& html = manager->GetHTML(false, false);
    AddResource(url, html, "text/html");
  }

  const bool synchronous_;
  const bool cancel_with_subnav_;

  MultiQueryManagerMap manager_map_;

  std::string cancel_url_;
};

}  // namespace

// Test that multiple frames can run many queries successfully in a synchronous
// manner.
TEST(MessageRouterTest, MultiQueryMultiFrameSync) {
  CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
      new MultiQueryMultiFrameTestHandler(true, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple frames can run many queries successfully in an
// asynchronous manner.
TEST(MessageRouterTest, MultiQueryMultiFrameAsync) {
  CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
      new MultiQueryMultiFrameTestHandler(false, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple frames can run many queries successfully in a synchronous
// manner. Cancel auto queries with sub-frame navigation.
TEST(MessageRouterTest, MultiQueryMultiFrameSyncSubnavCancel) {
  CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
      new MultiQueryMultiFrameTestHandler(true, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple frames can run many queries successfully in an
// asynchronous manner. Cancel auto queries with sub-frame navigation.
TEST(MessageRouterTest, MultiQueryMultiFrameAsyncSubnavCancel) {
  CefRefPtr<MultiQueryMultiFrameTestHandler> handler =
      new MultiQueryMultiFrameTestHandler(false, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

// Implementation of MRTestHandler that loads multiple pages and/or browsers and
// executes multiple queries.
class MultiQueryMultiLoadTestHandler
    : public MRTestHandler,
      public CefMessageRouterBrowserSide::Handler,
      public MultiQueryManagerMap::Observer,
      public MultiQueryManager::Observer {
 public:
  MultiQueryMultiLoadTestHandler(bool some, bool synchronous)
      : some_(some), synchronous_(synchronous) {
    manager_map_.AddObserver(this);
  }

  void OnLoadStart(CefRefPtr<CefBrowser> browser,
                   CefRefPtr<CefFrame> frame,
                   TransitionType transition_type) override {
    manager_map_.OnLoadStart(browser, frame);
  }

  void OnNotify(CefRefPtr<CefBrowser> browser,
                CefRefPtr<CefFrame> frame,
                const std::string& message) override {
    manager_map_.OnNotify(browser, frame, message);
  }

  bool OnQuery(CefRefPtr<CefBrowser> browser,
               CefRefPtr<CefFrame> frame,
               int64 query_id,
               const CefString& request,
               bool persistent,
               CefRefPtr<Callback> callback) override {
    return manager_map_.OnQuery(browser, frame, query_id, request, persistent,
                                callback);
  }

  void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
                       CefRefPtr<CefFrame> frame,
                       int64 query_id) override {
    manager_map_.OnQueryCanceled(browser, frame, query_id);
  }

  void OnMapManualQueriesCompleted(MultiQueryManagerMap* map) override {
    EXPECT_EQ(map, &manager_map_);
    if (manager_map_.HasAutoQueries()) {
      // Navigate all browsers somewhere else to terminate the auto queries.
      BrowserMap browser_map;
      GetAllBrowsers(&browser_map);

      BrowserMap::const_iterator it = browser_map.begin();
      for (; it != browser_map.end(); ++it) {
        it->second->GetMainFrame()->LoadURL(cancel_url_);
      }
    }
  }

  void OnMapAllQueriesCompleted(MultiQueryManagerMap* map) override {
    EXPECT_EQ(map, &manager_map_);
    DestroyTest();
  }

  void DestroyTest() override {
    manager_map_.AssertAllComplete();
    TestHandler::DestroyTest();
  }

 protected:
  void AddHandlers(
      CefRefPtr<CefMessageRouterBrowserSide> message_router) override {
    message_router->AddHandler(this, false);
  }

  void AddManagedResource(const std::string& url,
                          bool assert_total,
                          bool assert_browser) {
    MultiQueryManager* manager = manager_map_.CreateManager(url, synchronous_);
    manager->AddObserver(this);
    MakeTestQueries(manager, some_, 75);

    const std::string& html = manager->GetHTML(assert_total, assert_browser);
    AddResource(url, html, "text/html");
  }

  void Finalize() {
    manager_map_.Finalize();

    if (manager_map_.HasAutoQueries()) {
      cancel_url_ = std::string(kTestDomain1) + "cancel.html";
      AddResource(cancel_url_, "<html><body>cancel</body></html>", "text/html");
    }
  }

  MultiQueryManagerMap manager_map_;

 private:
  const bool some_;
  const bool synchronous_;

  std::string cancel_url_;
};

// Test multiple browsers that send queries at the same time.
class MultiQueryMultiBrowserTestHandler
    : public MultiQueryMultiLoadTestHandler {
 public:
  MultiQueryMultiBrowserTestHandler(bool synchronous, bool same_origin)
      : MultiQueryMultiLoadTestHandler(false, synchronous),
        same_origin_(same_origin) {}

 protected:
  void RunMRTest() override {
    const std::string& url1 = std::string(kTestDomain1) + "browser1.html";
    const std::string& url2 =
        std::string(same_origin_ ? kTestDomain1 : kTestDomain2) +
        "browser2.html";
    const std::string& url3 =
        std::string(same_origin_ ? kTestDomain1 : kTestDomain3) +
        "browser3.html";

    AddManagedResource(url1, false, true);
    AddManagedResource(url2, false, true);
    AddManagedResource(url3, false, true);
    Finalize();

    // Create 2 browsers simultaniously.
    CreateBrowser(url1, nullptr);
    CreateBrowser(url2, nullptr);
    CreateBrowser(url3, nullptr);
  }

 private:
  bool same_origin_;
};

}  // namespace

// Test that multiple browsers can query simultaniously from the same origin.
TEST(MessageRouterTest, MultiQueryMultiBrowserSameOriginSync) {
  CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
      new MultiQueryMultiBrowserTestHandler(true, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple browsers can query simultaniously from the same origin.
TEST(MessageRouterTest, MultiQueryMultiBrowserSameOriginAsync) {
  CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
      new MultiQueryMultiBrowserTestHandler(false, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple browsers can query simultaniously from different origins.
TEST(MessageRouterTest, MultiQueryMultiBrowserDifferentOriginSync) {
  CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
      new MultiQueryMultiBrowserTestHandler(true, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple browsers can query simultaniously from different origins.
TEST(MessageRouterTest, MultiQueryMultiBrowserDifferentOriginAsync) {
  CefRefPtr<MultiQueryMultiBrowserTestHandler> handler =
      new MultiQueryMultiBrowserTestHandler(false, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

namespace {

// Test multiple navigations that send queries sequentially.
class MultiQueryMultiNavigateTestHandler
    : public MultiQueryMultiLoadTestHandler {
 public:
  MultiQueryMultiNavigateTestHandler(bool synchronous, bool same_origin)
      : MultiQueryMultiLoadTestHandler(false, synchronous),
        same_origin_(same_origin) {}

  void OnManualQueriesCompleted(MultiQueryManager* manager) override {
    const std::string& url = manager->label();
    if (url == url1_)  // 2. Load the 2nd url.
      GetBrowser()->GetMainFrame()->LoadURL(url2_);
    else if (url == url2_)  // 3. Load the 3rd url.
      GetBrowser()->GetMainFrame()->LoadURL(url3_);
  }

 protected:
  void RunMRTest() override {
    url1_ = std::string(kTestDomain1) + "browser1.html";
    url2_ = std::string(same_origin_ ? kTestDomain1 : kTestDomain2) +
            "browser2.html";
    url3_ = std::string(same_origin_ ? kTestDomain1 : kTestDomain3) +
            "browser3.html";

    AddManagedResource(url1_, true, true);
    AddManagedResource(url2_, true, true);
    AddManagedResource(url3_, true, true);
    Finalize();

    // 1. Load the 1st url.
    CreateBrowser(url1_, nullptr);
  }

 private:
  bool same_origin_;

  std::string url1_;
  std::string url2_;
  std::string url3_;
};

}  // namespace

// Test that multiple navigations can query from the same origin.
TEST(MessageRouterTest, MultiQueryMultiNavigateSameOriginSync) {
  CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
      new MultiQueryMultiNavigateTestHandler(true, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple navigations can query from the same origin.
TEST(MessageRouterTest, MultiQueryMultiNavigateSameOriginAsync) {
  CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
      new MultiQueryMultiNavigateTestHandler(false, true);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple navigations can query from different origins.
TEST(MessageRouterTest, MultiQueryMultiNavigateDifferentOriginSync) {
  CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
      new MultiQueryMultiNavigateTestHandler(true, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}

// Test that multiple navigations can query from different origins.
TEST(MessageRouterTest, MultiQueryMultiNavigateDifferentOriginAsync) {
  CefRefPtr<MultiQueryMultiNavigateTestHandler> handler =
      new MultiQueryMultiNavigateTestHandler(false, false);
  handler->ExecuteTest();
  ReleaseAndWaitForDestructor(handler);
}
