cef/tests/ceftests/message_router_unittest.cc
Marshall Greenblatt 241941a44a Move message routing from CefBrowser to CefFrame (see issue ).
This change moves the SendProcessMessage method from CefBrowser to CefFrame and
adds CefBrowser parameters to OnProcessMessageReceived and
OnDraggableRegionsChanged.

The internal implementation has changed as follows:
- Frame IDs are now a 64-bit combination of the 32-bit render_process_id and
  render_routing_id values that uniquely identify a RenderFrameHost (RFH).
- CefFrameHostImpl objects are now managed by CefBrowserInfo with life span tied
  to RFH expectations. Specifically, a CefFrameHostImpl object representing a
  sub-frame will be created when a RenderFrame is created in the renderer
  process and detached when the associated RenderFrame is deleted or the
  renderer process in which it runs has died.
- The CefFrameHostImpl object representing the main frame will always be valid
  but the underlying RFH (and associated frame ID) may change over time as a
  result of cross-origin navigations. Despite these changes calling LoadURL on
  the main frame object in the browser process will always navigate as expected.
- Speculative RFHs, which may be created as a result of a cross-origin
  navigation and discarded if that navigation is not committed, are now handled
  correctly (e.g. ignored in most cases until they're committed).
- It is less likely, but still possible, to receive a CefFrame object with an
  invalid frame ID (ID < 0). This can happen in cases where a RFH has not yet
  been created for a sub-frame. For example, when OnBeforeBrowse is called
  before initiating navigation in a previously nonexisting sub-frame.

To test: All tests pass with NetworkService enabled and disabled.
2019-05-29 17:44:56 +03:00

2928 lines
92 KiB
C++

// 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(NULL, NULL);
} else if (name == kJSAssertBrowserCountFunc) {
actual_count =
delegate_->message_router_->GetPendingCount(browser, NULL);
} 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 = browser->GetMainFrame()->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();
}
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_, NULL);
}
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_ = NULL;
}
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_ = NULL;
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_ = NULL;
}
}
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_ = NULL;
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 = NULL;
// 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(NULL, NULL, 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(NULL, NULL, 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(NULL, NULL, 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, NULL);
CreateBrowser(url2, NULL);
CreateBrowser(url3, NULL);
}
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_, NULL);
}
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);
}