cef/tests/ceftests/frame_handler_unittest.cc

1741 lines
54 KiB
C++

// Copyright 2021 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 <limits>
#include <map>
#include <memory>
#include <queue>
#include <sstream>
#include <string>
#include "include/base/cef_callback.h"
#include "include/wrapper/cef_closure_task.h"
#include "tests/ceftests/routing_test_handler.h"
#include "tests/ceftests/test_handler.h"
#include "tests/ceftests/test_util.h"
#include "tests/gtest/include/gtest/gtest.h"
// Set to 1 to enable verbose debugging info logging.
#define VERBOSE_DEBUGGING 0
namespace {
// Tracks callback status for a single frame object.
struct FrameStatus {
// Callbacks in expected order. Not all callbacks are executed in all cases
// (see IsExpectedCallback).
enum CallbackType {
FRAME_CREATED,
MAIN_FRAME_INITIAL_ASSIGNED,
AFTER_CREATED,
FRAME_ATTACHED,
MAIN_FRAME_CHANGED_ASSIGNED,
LOAD_START,
LOAD_END,
BEFORE_CLOSE,
FRAME_DETACHED,
MAIN_FRAME_CHANGED_REMOVED,
MAIN_FRAME_FINAL_REMOVED,
CALLBACK_LAST = MAIN_FRAME_FINAL_REMOVED,
};
static const char* GetCallbackName(int type) {
switch (type) {
case FRAME_CREATED:
return "OnFrameCreated";
case MAIN_FRAME_INITIAL_ASSIGNED:
return "OnMainFrameChanged(initial_assigned)";
case AFTER_CREATED:
return "OnAfterCreated";
case FRAME_ATTACHED:
return "OnFrameAttached";
case MAIN_FRAME_CHANGED_ASSIGNED:
return "OnMainFrameChanged(changed_assigned)";
case LOAD_START:
return "OnLoadStart";
case LOAD_END:
return "OnLoadEnd";
case BEFORE_CLOSE:
return "OnBeforeClose";
case FRAME_DETACHED:
return "OnFrameDetached";
case MAIN_FRAME_CHANGED_REMOVED:
return "OnMainFrameChanged(changed_removed)";
case MAIN_FRAME_FINAL_REMOVED:
return "OnMainFrameChanged(final_removed)";
}
NOTREACHED();
return "Unknown";
}
// Returns true for callbacks that should only execute for main frames.
static bool IsMainFrameOnlyCallback(int type) {
return (type == MAIN_FRAME_INITIAL_ASSIGNED || type == AFTER_CREATED ||
type == MAIN_FRAME_CHANGED_ASSIGNED || type == BEFORE_CLOSE ||
type == MAIN_FRAME_CHANGED_REMOVED ||
type == MAIN_FRAME_FINAL_REMOVED);
}
static std::string GetFrameDebugString(CefRefPtr<CefFrame> frame) {
// Match the logic in frame_util::GetFrameDebugString.
// Specific formulation of the frame ID is an implementation detail that
// should generally not be relied upon, but a consistent format makes the
// debug logging easier to follow.
return frame->GetIdentifier();
}
explicit FrameStatus(CefRefPtr<CefFrame> frame)
: frame_id_(frame->GetIdentifier()),
is_main_(frame->IsMain()),
ident_str_(GetFrameDebugString(frame)) {}
std::string frame_id() const { return frame_id_; }
bool is_main() const { return is_main_; }
bool AllQueriesDelivered(std::string* msg = nullptr) const {
EXPECT_UI_THREAD();
const int expected_ct = is_temporary_ ? 0 : expected_query_ct_;
#if VERBOSE_DEBUGGING
if (msg) {
std::stringstream ss;
ss << ident_str_ << "(expected=" << expected_ct
<< " delivered=" << delivered_query_ct_ << ")";
*msg += ss.str();
}
#endif
return delivered_query_ct_ == expected_ct;
}
int QueriesDeliveredCount() const {
EXPECT_UI_THREAD();
return delivered_query_ct_;
}
bool IsSame(CefRefPtr<CefFrame> frame) const {
return frame->GetIdentifier().ToString() == frame_id();
}
bool IsLoaded(std::string* msg = nullptr) const {
#if VERBOSE_DEBUGGING
if (msg) {
std::stringstream ss;
ss << ident_str_ << "(";
for (int i = 0; i <= LOAD_END; ++i) {
ss << GetCallbackName(i) << "=" << got_callback_[i];
if (i < LOAD_END) {
ss << " ";
}
}
ss << ")";
*msg += ss.str();
}
#endif
return got_callback_[LOAD_END];
}
bool IsDetached() const { return got_callback_[FRAME_DETACHED]; }
void SetIsFirstMain(bool val) {
EXPECT_TRUE(is_main_);
is_first_main_ = val;
if (is_first_main_) {
// Also expect OnAfterCreated
expected_query_ct_++;
}
}
void SetIsLastMain(bool val) {
EXPECT_TRUE(is_main_);
is_last_main_ = val;
}
void SetIsTemporary(bool val) {
EXPECT_FALSE(is_main_);
is_temporary_ = val;
}
bool IsTemporary() const { return is_temporary_; }
void SetAdditionalDebugInfo(const std::string& debug_info) {
debug_info_ = debug_info;
}
std::string GetDebugString() const { return debug_info_ + ident_str_; }
// The main frame will be reused for same-origin navigations.
void ResetMainLoadStatus() {
EXPECT_TRUE(is_main_);
ResetCallbackStatus(LOAD_START, /*expect_query=*/true);
ResetCallbackStatus(LOAD_END, /*expect_query=*/true);
}
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, FRAME_CREATED);
// Test delivery of messages using a frame that isn't connected yet.
// This tests queuing of messages in the browser process and possibly the
// renderer process.
ExecuteQuery(frame, FRAME_CREATED);
}
void OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, FRAME_ATTACHED);
// Test delivery of messages using a frame that just connected.
// This tests queuing of messages in the browser process and possibly the
// renderer process.
ExecuteQuery(frame, FRAME_ATTACHED);
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
// A frame is never valid after it's detached.
VerifyFrame(__FUNCTION__, frame, /*expect_valid=*/false);
GotCallback(__FUNCTION__, FRAME_DETACHED);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) {
EXPECT_UI_THREAD();
EXPECT_TRUE(is_main_);
VerifyBrowser(__FUNCTION__, browser);
bool got_match = false;
if (old_frame && new_frame) {
EXPECT_STRNE(old_frame->GetIdentifier().ToString().c_str(),
new_frame->GetIdentifier().ToString().c_str());
}
if (old_frame && IsSame(old_frame)) {
got_match = true;
// A frame is never valid after it's detached.
VerifyFrame(__FUNCTION__, old_frame, /*expect_valid=*/false);
GotCallback(__FUNCTION__, new_frame ? MAIN_FRAME_CHANGED_REMOVED
: MAIN_FRAME_FINAL_REMOVED);
if (is_last_main_) {
EXPECT_FALSE(new_frame);
}
}
if (new_frame && IsSame(new_frame)) {
got_match = true;
VerifyFrame(__FUNCTION__, new_frame);
GotCallback(__FUNCTION__, old_frame ? MAIN_FRAME_CHANGED_ASSIGNED
: MAIN_FRAME_INITIAL_ASSIGNED);
if (is_first_main_) {
EXPECT_FALSE(old_frame);
}
}
EXPECT_TRUE(got_match);
}
void OnAfterCreated(CefRefPtr<CefBrowser> browser) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
auto frame = browser->GetMainFrame();
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, AFTER_CREATED);
ExecuteQuery(frame, AFTER_CREATED);
}
// Called for all existing frames, not just the target frame.
// We need to track this status to know if the browser should be valid in
// following calls to OnFrameDetached.
void OnBeforeClose(CefRefPtr<CefBrowser> browser) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
auto frame = browser->GetMainFrame();
EXPECT_TRUE(frame->IsValid());
got_before_close_ = true;
if (IsSame(frame)) {
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, BEFORE_CLOSE);
}
}
void OnLoadStart(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, LOAD_START);
ExecuteQuery(frame, LOAD_START);
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
VerifyBrowser(__FUNCTION__, browser);
VerifyFrame(__FUNCTION__, frame);
GotCallback(__FUNCTION__, LOAD_END);
ExecuteQuery(frame, LOAD_END);
}
void OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
const CefString& request) {
EXPECT_UI_THREAD();
const std::string& received_query = request;
#if VERBOSE_DEBUGGING
LOG(INFO) << GetDebugString() << " recv query " << received_query << " ("
<< (delivered_query_ct_ + 1) << " of " << expected_query_ct_
<< ")";
#endif
VerifyBrowser(__FUNCTION__, browser);
VerifyFrame(__FUNCTION__, frame);
EXPECT_GE(pending_queries_.size(), 1U);
const std::string& expected_query = pending_queries_.front();
EXPECT_STREQ(expected_query.c_str(), received_query.c_str());
if (expected_query == received_query) {
pending_queries_.pop();
}
EXPECT_LT(delivered_query_ct_, expected_query_ct_);
delivered_query_ct_++;
}
void VerifyTestResults() {
EXPECT_UI_THREAD();
// Verify that all expected callbacks have executed.
VerifyCallbackStatus(__FUNCTION__, CALLBACK_LAST + 1);
if (is_temporary_) {
// Should not receive any queries.
EXPECT_FALSE(is_main_);
EXPECT_EQ(0, delivered_query_ct_);
} else {
// Verify that all expected messages have been sent and received.
EXPECT_EQ(expected_query_ct_, delivered_query_ct_);
EXPECT_EQ(0U, pending_queries_.size());
while (!pending_queries_.empty()) {
ADD_FAILURE() << "Query sent but not received: "
<< pending_queries_.front();
pending_queries_.pop();
}
}
}
bool DidGetCallback(int callback) const { return got_callback_[callback]; }
private:
void GotCallback(const std::string& func, int callback) {
#if VERBOSE_DEBUGGING
LOG(INFO) << GetDebugString() << " callback " << GetCallbackName(callback);
#endif
EXPECT_TRUE(IsExpectedCallback(callback)) << func;
VerifyCallbackStatus(func, callback);
got_callback_[callback].yes();
}
bool IsExpectedCallback(int callback) const {
if (!is_main_ && IsMainFrameOnlyCallback(callback)) {
return false;
}
if (is_main_) {
if ((callback == MAIN_FRAME_INITIAL_ASSIGNED ||
callback == AFTER_CREATED) &&
!is_first_main_) {
return false;
}
if ((callback == BEFORE_CLOSE || callback == MAIN_FRAME_FINAL_REMOVED) &&
!is_last_main_) {
return false;
}
if (callback == MAIN_FRAME_CHANGED_ASSIGNED && is_first_main_) {
return false;
}
if (callback == MAIN_FRAME_CHANGED_REMOVED && is_last_main_) {
return false;
}
} else if (is_temporary_) {
// For cross-process sub-frame navigation a sub-frame is first created in
// the parent's renderer process. That sub-frame is then discarded after
// the real cross-origin sub-frame is created in a different renderer
// process. These discarded sub-frames will get OnFrameCreated/
// OnFrameAttached immediately followed by OnFrameDetached.
return callback == FRAME_CREATED || callback == FRAME_ATTACHED ||
callback == FRAME_DETACHED;
}
return true;
}
void VerifyCallbackStatus(const std::string& func,
int current_callback) const {
EXPECT_UI_THREAD();
for (int i = 0; i <= CALLBACK_LAST; ++i) {
if (i < current_callback && IsExpectedCallback(i)) {
if (i == FRAME_ATTACHED &&
(current_callback == MAIN_FRAME_CHANGED_ASSIGNED ||
current_callback == LOAD_START || current_callback == LOAD_END)) {
// Timing of OnFrameAttached is flaky. See issue #3817.
continue;
}
EXPECT_TRUE(got_callback_[i])
<< "inside " << func << " should already have gotten "
<< GetCallbackName(i);
} else {
if (current_callback == FRAME_ATTACHED &&
(i == MAIN_FRAME_CHANGED_ASSIGNED || i == LOAD_START ||
i == LOAD_END)) {
// Timing of OnFrameAttached is flaky. See issue #3817.
continue;
}
EXPECT_FALSE(got_callback_[i])
<< "inside " << func << " should not already have gotten "
<< GetCallbackName(i);
}
}
}
void VerifyBrowser(const std::string& func,
CefRefPtr<CefBrowser> browser) const {
const bool expect_valid = !got_before_close_;
if (expect_valid) {
EXPECT_TRUE(browser->IsValid()) << func;
} else {
EXPECT_FALSE(browser->IsValid()) << func;
}
const auto browser_id = browser->GetIdentifier();
EXPECT_GT(browser_id, 0) << func;
auto get_browser = CefBrowserHost::GetBrowserByIdentifier(browser_id);
EXPECT_TRUE(get_browser && get_browser->IsSame(browser)) << func;
// Note that this might not be the same main frame as us when navigating
// cross-origin, because the new main frame object is assigned to the
// browser before the CefFrameHandler callbacks related to main frame change
// have executed. This started out as an implementation detail but it fits
// nicely with the concept that "GetMainFrame() always returns a frame that
// can be used", which wouldn't be the case if we returned the old frame
// when calling GetMainFrame() from inside OnFrameCreated (for the new
// frame), OnFrameDetached (for the old frame) or OnMainFrameChanged.
auto main_frame = browser->GetMainFrame();
if (expect_valid) {
EXPECT_TRUE(main_frame) << func;
if (main_frame) {
EXPECT_TRUE(main_frame->IsValid()) << func;
EXPECT_TRUE(main_frame->IsMain()) << func;
}
} else {
// GetMainFrame() returns nullptr after OnBeforeClose.
EXPECT_FALSE(main_frame) << func;
}
}
void VerifyFrame(const std::string& func,
CefRefPtr<CefFrame> frame,
bool expect_valid = true) const {
if (expect_valid) {
EXPECT_TRUE(frame->IsValid()) << func;
} else {
EXPECT_FALSE(frame->IsValid()) << func;
}
// |frame| should be us. This checks the frame type and ID.
EXPECT_STREQ(ident_str_.c_str(), GetFrameDebugString(frame).c_str())
<< func;
}
void ExecuteQuery(CefRefPtr<CefFrame> frame, int callback) {
EXPECT_UI_THREAD();
const std::string& value = GetCallbackName(callback);
std::string js_string;
#if VERBOSE_DEBUGGING
LOG(INFO) << GetDebugString() << " sent query " << value;
js_string +=
"console.log('" + GetDebugString() + " exec query " + value + "');";
#endif
js_string += "window.testQuery({request:'" + value + "'});";
pending_queries_.push(value);
// GetURL() will return an empty string for early callbacks, but that
// doesn't appear to cause any issues.
frame->ExecuteJavaScript(js_string, frame->GetURL(), 0);
}
// Reset state so we can get the same callback again.
void ResetCallbackStatus(int callback, bool expect_query) {
EXPECT_UI_THREAD();
EXPECT_TRUE(got_callback_[callback]) << GetCallbackName(callback);
got_callback_[callback].reset();
if (expect_query) {
delivered_query_ct_--;
}
}
const std::string frame_id_;
const bool is_main_;
const std::string ident_str_;
bool is_first_main_ = false;
bool is_last_main_ = false;
bool is_temporary_ = false;
std::string debug_info_;
bool got_before_close_ = false;
TrackCallback got_callback_[CALLBACK_LAST + 1];
std::queue<std::string> pending_queries_;
// Expect OnCreated, OnAttached, OnLoadStart, OnLoadEnd.
int expected_query_ct_ = 4;
int delivered_query_ct_ = 0;
};
const char kOrderMainUrl[] = "https://tests-frame-handler/main-order.html";
class OrderMainTestHandler : public RoutingTestHandler, public CefFrameHandler {
public:
explicit OrderMainTestHandler(CompletionState* completion_state = nullptr)
: RoutingTestHandler(completion_state) {}
CefRefPtr<CefFrameHandler> GetFrameHandler() override {
get_frame_handler_ct_++;
return this;
}
void RunTest() override {
// Add the main resource that we will navigate to/from.
AddResource(GetMainURL(), GetMainHtml(), "text/html");
// Create the browser.
CreateBrowser(GetMainURL());
// Time out the test after a reasonable period of time.
SetTestTimeout();
}
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
EXPECT_UI_THREAD();
RoutingTestHandler::OnAfterCreated(browser);
EXPECT_FALSE(got_after_created_);
got_after_created_ = true;
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnAfterCreated(browser);
}
void OnLoadStart(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
TransitionType transition_type) override {
EXPECT_UI_THREAD();
RoutingTestHandler::OnLoadStart(browser, frame, transition_type);
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnLoadStart(browser, frame);
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
EXPECT_UI_THREAD();
RoutingTestHandler::OnLoadEnd(browser, frame, httpStatusCode);
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnLoadEnd(browser, frame);
MaybeDestroyTest();
}
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
EXPECT_UI_THREAD();
EXPECT_FALSE(got_before_close_);
got_before_close_ = true;
EXPECT_TRUE(current_main_frame_);
if (current_main_frame_) {
current_main_frame_->OnBeforeClose(browser);
}
RoutingTestHandler::OnBeforeClose(browser);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
EXPECT_UI_THREAD();
// Messages for the old and new frames are interleaved during cross-origin
// navigation.
if (pending_main_frame_) {
EXPECT_TRUE(IsCrossOriginOrSameSiteBFCacheEnabled());
pending_main_frame_->OnQuery(browser, frame, request);
} else {
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnQuery(browser, frame, request);
}
MaybeDestroyTest();
return true;
}
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
EXPECT_UI_THREAD();
EXPECT_TRUE(frame->IsMain());
EXPECT_FALSE(pending_main_frame_);
// First callback referencing the new frame.
pending_main_frame_ = new FrameStatus(frame);
pending_main_frame_->SetAdditionalDebugInfo(GetAdditionalDebugInfo());
pending_main_frame_->SetIsFirstMain(!got_after_created_);
pending_main_frame_->SetIsLastMain(
!IsCrossOriginOrSameSiteBFCacheEnabled() || IsLastNavigation());
pending_main_frame_->OnFrameCreated(browser, frame);
}
void OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
bool reattached) override {
EXPECT_UI_THREAD();
// May arrive before or after OnMainFrameChanged switches the frame (after
// on initial browser creation, before on cross-origin navigation).
if (pending_main_frame_) {
EXPECT_TRUE(IsCrossOriginOrSameSiteBFCacheEnabled());
pending_main_frame_->OnFrameAttached(browser, frame);
} else {
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnFrameAttached(browser, frame);
}
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
EXPECT_UI_THREAD();
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnFrameDetached(browser, frame);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) override {
EXPECT_UI_THREAD();
EXPECT_TRUE(old_frame || new_frame);
if (old_frame) {
EXPECT_FALSE(old_frame->IsValid());
EXPECT_TRUE(old_frame->IsMain());
// May be nullptr with PopupOrderMainTestHandler.
if (current_main_frame_) {
// Last callback referencing the old frame.
EXPECT_TRUE(current_main_frame_);
current_main_frame_->OnMainFrameChanged(browser, old_frame, new_frame);
current_main_frame_->VerifyTestResults();
delete current_main_frame_;
current_main_frame_ = nullptr;
}
}
if (new_frame) {
EXPECT_TRUE(new_frame->IsValid());
EXPECT_TRUE(new_frame->IsMain());
// Always called after OnFrameCreated. See also comments in
// OnFrameAttached.
EXPECT_TRUE(pending_main_frame_);
pending_main_frame_->OnMainFrameChanged(browser, old_frame, new_frame);
// The pending frame becomes the current frame.
EXPECT_FALSE(current_main_frame_);
current_main_frame_ = pending_main_frame_;
pending_main_frame_ = nullptr;
}
if (old_frame && new_frame) {
// Main frame changed due to cross-origin navigation.
EXPECT_TRUE(IsCrossOriginOrSameSiteBFCacheEnabled());
main_frame_changed_ct_++;
}
if (old_frame && !new_frame) {
// Very last callback.
VerifyTestResults();
}
}
protected:
virtual std::string GetMainURL() const { return kOrderMainUrl; }
virtual std::string GetMainHtml() const {
return "<html><body>TEST</body></html>";
}
virtual std::string GetNextMainURL() { return std::string(); }
virtual bool IsFirstNavigation() const { return true; }
virtual bool IsLastNavigation() const { return true; }
virtual bool IsCrossOrigin() const { return false; }
bool IsCrossOriginOrSameSiteBFCacheEnabled() const {
return IsCrossOrigin() || IsSameSiteBFCacheEnabled();
}
virtual std::string GetAdditionalDebugInfo() const { return std::string(); }
virtual bool AllQueriesDelivered(std::string* msg = nullptr) const {
EXPECT_UI_THREAD();
if (pending_main_frame_) {
return false;
}
return current_main_frame_->AllQueriesDelivered(msg);
}
virtual bool AllFramesLoaded(std::string* msg = nullptr) const {
EXPECT_UI_THREAD();
if (pending_main_frame_) {
return false;
}
return current_main_frame_->IsLoaded(msg);
}
void MaybeDestroyTest() {
#if VERBOSE_DEBUGGING
std::string delivered_msg, loaded_msg;
const bool all_queries_delivered = AllQueriesDelivered(&delivered_msg);
const bool all_frames_loaded = AllFramesLoaded(&loaded_msg);
LOG(INFO) << (current_main_frame_ ? current_main_frame_->GetDebugString()
: "")
<< " AllQueriesDelivered=" << all_queries_delivered << " {"
<< delivered_msg << "}"
<< " AllFramesLoaded=" << all_frames_loaded << " {" << loaded_msg
<< "}";
if (all_queries_delivered && all_frames_loaded) {
#else
if (AllQueriesDelivered() && AllFramesLoaded()) {
#endif
const std::string& next_url = GetNextMainURL();
if (!next_url.empty()) {
if (!IsCrossOriginOrSameSiteBFCacheEnabled()) {
// Reusing the same main frame for same origin nav.
current_main_frame_->ResetMainLoadStatus();
}
#if VERBOSE_DEBUGGING
LOG(INFO) << current_main_frame_->GetDebugString()
<< "--> Navigating to " << next_url;
#endif
GetBrowser()->GetMainFrame()->LoadURL(next_url);
} else {
#if VERBOSE_DEBUGGING
LOG(INFO) << "--> Destroy test";
#endif
DestroyTest();
}
}
}
virtual void VerifyTestResults() {
EXPECT_UI_THREAD();
// OnMainFrameChanged should have cleaned up.
EXPECT_FALSE(pending_main_frame_);
EXPECT_FALSE(current_main_frame_);
EXPECT_TRUE(got_after_created_);
EXPECT_TRUE(got_before_close_);
// We document GetFrameHandler() as only being called a single time.
EXPECT_EQ(1, get_frame_handler_ct_);
// Make sure we get the expected number OnMainFrameChanged callbacks for the
// main frame.
EXPECT_EQ(expected_main_frame_changed_ct_, main_frame_changed_ct_);
}
// Number of times we expect the main frame to change (e.g. once per
// cross-origin navigation).
int expected_main_frame_changed_ct_ = 0;
bool got_after_created_ = false;
bool got_before_close_ = false;
private:
int get_frame_handler_ct_ = 0;
int main_frame_changed_ct_ = 0;
FrameStatus* current_main_frame_ = nullptr;
FrameStatus* pending_main_frame_ = nullptr;
IMPLEMENT_REFCOUNTING(OrderMainTestHandler);
};
} // namespace
// Test the ordering and behavior of main frame callbacks.
TEST(FrameHandlerTest, OrderMain) {
CefRefPtr<OrderMainTestHandler> handler = new OrderMainTestHandler();
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
const char kOrderMainUrlPrefix[] = "https://tests-frame-handler";
class NavigateOrderMainTestHandler : public OrderMainTestHandler {
public:
explicit NavigateOrderMainTestHandler(bool cross_origin,
int additional_nav_ct = 2)
: cross_origin_(cross_origin), additional_nav_ct_(additional_nav_ct) {}
void RunTest() override {
// Once for each cross-origin LoadURL call.
expected_main_frame_changed_ct_ =
IsCrossOriginOrSameSiteBFCacheEnabled() ? additional_nav_ct_ : 0;
// Resources for the 2nd+ navigation.
for (int i = 1; i <= additional_nav_ct_; i++) {
AddResource(GetURLForNav(i), GetMainHtmlForNav(i), "text/html");
}
OrderMainTestHandler::RunTest();
}
protected:
// Loaded when the browser is created.
std::string GetMainURL() const override { return GetURLForNav(0); }
std::string GetMainHtml() const override { return GetMainHtmlForNav(0); }
std::string GetNextMainURL() override {
if (current_nav_ct_ == additional_nav_ct_) {
// No more navigations.
return std::string();
}
return GetURLForNav(++current_nav_ct_);
}
bool IsFirstNavigation() const override { return current_nav_ct_ == 0U; }
bool IsLastNavigation() const override {
return current_nav_ct_ == additional_nav_ct_;
}
bool IsCrossOrigin() const override { return cross_origin_; }
int additional_nav_ct() const { return additional_nav_ct_; }
void VerifyTestResults() override {
OrderMainTestHandler::VerifyTestResults();
EXPECT_TRUE(IsLastNavigation());
}
virtual std::string GetMainHtmlForNav(int nav) const {
return "<html><body>TEST " + std::to_string(nav) + "</body></html>";
}
std::string GetURLForNav(int nav, const std::string& suffix = "") const {
std::stringstream ss;
if (cross_origin_) {
ss << kOrderMainUrlPrefix << nav << "/cross-origin" << suffix << ".html";
} else {
ss << kOrderMainUrlPrefix << "/" << nav << "same-origin" << suffix
<< ".html";
}
return ss.str();
}
private:
const bool cross_origin_;
const int additional_nav_ct_;
int current_nav_ct_ = 0;
};
} // namespace
// Main frame navigating to different URLs with the same origin.
TEST(FrameHandlerTest, OrderMainNavSameOrigin) {
CefRefPtr<NavigateOrderMainTestHandler> handler =
new NavigateOrderMainTestHandler(/*cross_origin=*/false);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame navigating cross-origin.
TEST(FrameHandlerTest, OrderMainNavCrossOrigin) {
CefRefPtr<NavigateOrderMainTestHandler> handler =
new NavigateOrderMainTestHandler(/*cross_origin=*/true);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
// Tracks sub-frames for a single main frame load.
class FrameStatusMap {
public:
explicit FrameStatusMap(size_t expected_frame_ct)
: expected_frame_ct_(expected_frame_ct) {}
~FrameStatusMap() { EXPECT_TRUE(frame_map_.empty()); }
bool Contains(CefRefPtr<CefFrame> frame) const {
return frame_map_.find(frame->GetIdentifier()) != frame_map_.end();
}
FrameStatus* CreateFrameStatus(CefRefPtr<CefFrame> frame) {
EXPECT_UI_THREAD();
EXPECT_LT(size(), expected_frame_ct_);
const std::string& id = frame->GetIdentifier();
EXPECT_TRUE(!id.empty());
EXPECT_EQ(frame_map_.find(id), frame_map_.end());
FrameStatus* status = new FrameStatus(frame);
frame_map_.insert(std::make_pair(id, status));
return status;
}
FrameStatus* GetFrameStatus(CefRefPtr<CefFrame> frame) const {
EXPECT_UI_THREAD();
const std::string& id = frame->GetIdentifier();
EXPECT_TRUE(!id.empty());
Map::const_iterator it = frame_map_.find(id);
EXPECT_NE(it, frame_map_.end());
return it->second;
}
void RemoveFrameStatus(CefRefPtr<CefFrame> frame) {
const std::string& id = frame->GetIdentifier();
Map::iterator it = frame_map_.find(id);
EXPECT_NE(it, frame_map_.end());
frame_map_.erase(it);
}
void OnBeforeClose(CefRefPtr<CefBrowser> browser) {
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
it->second->OnBeforeClose(browser);
}
}
bool AllQueriesDelivered(std::string* msg = nullptr) const {
if (size() != expected_frame_ct_) {
#if VERBOSE_DEBUGGING
if (msg) {
std::stringstream ss;
ss << " SUB COUNT MISMATCH! size=" << size()
<< " expected=" << expected_frame_ct_;
*msg += ss.str();
}
#endif
return false;
}
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
if (!it->second->AllQueriesDelivered(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " " + it->second->GetDebugString() + " PENDING";
}
#endif
return false;
}
}
return true;
}
bool AllFramesLoaded(std::string* msg = nullptr) const {
if (size() != expected_frame_ct_) {
#if VERBOSE_DEBUGGING
if (msg) {
std::stringstream ss;
ss << " SUB COUNT MISMATCH! size=" << size()
<< " expected=" << expected_frame_ct_;
*msg += ss.str();
}
#endif
return false;
}
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
if (!it->second->IsTemporary() && !it->second->IsLoaded(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " " + it->second->GetDebugString() + " PENDING";
}
#endif
return false;
}
}
return true;
}
bool AllFramesDetached() const {
if (size() != expected_frame_ct_) {
return false;
}
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
if (!it->second->IsDetached()) {
return false;
}
}
return true;
}
void VerifyAndClearTestResults() {
EXPECT_EQ(expected_frame_ct_, size());
Map::const_iterator it = frame_map_.begin();
for (; it != frame_map_.end(); ++it) {
it->second->VerifyTestResults();
delete it->second;
}
frame_map_.clear();
}
size_t size() const { return frame_map_.size(); }
private:
// Map of frame ID to status object.
using Map = std::map<std::string, FrameStatus*>;
Map frame_map_;
// The expected number of sub-frames.
const size_t expected_frame_ct_;
};
class OrderSubTestHandler : public NavigateOrderMainTestHandler {
public:
enum TestMode {
// Two sub-frames at the same level.
SUBFRAME_PEERS,
// One sub-frame inside the other.
SUBFRAME_CHILDREN,
};
OrderSubTestHandler(bool cross_origin,
int additional_nav_ct,
TestMode mode,
size_t expected_frame_ct = 2U)
: NavigateOrderMainTestHandler(cross_origin, additional_nav_ct),
test_mode_(mode),
expected_frame_ct_(expected_frame_ct) {}
~OrderSubTestHandler() override { EXPECT_TRUE(frame_maps_.empty()); }
void RunTest() override {
for (int i = 0; i <= additional_nav_ct(); i++) {
AddResource(GetSubURL1ForNav(i), GetSubFrameHtml1ForNav(i), "text/html");
AddResource(GetSubURL2ForNav(i), GetSubFrameHtml2ForNav(i), "text/html");
}
NavigateOrderMainTestHandler::RunTest();
}
void OnBeforeClose(CefRefPtr<CefBrowser> browser) override {
NavigateOrderMainTestHandler::OnBeforeClose(browser);
for (auto& map : frame_maps_) {
// Also need to notify any sub-frames.
map->OnBeforeClose(browser);
}
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnQuery(browser, frame, request);
if (status->AllQueriesDelivered()) {
MaybeDestroyTest();
}
return true;
}
return NavigateOrderMainTestHandler::OnQuery(browser, frame, query_id,
request, persistent, callback);
}
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (!frame->IsMain()) {
// Potentially the first notification of a new sub-frame after navigation.
auto map = GetOrCreateFrameMap(frame);
auto status = map->CreateFrameStatus(frame);
status->SetAdditionalDebugInfo(GetAdditionalDebugInfo());
status->OnFrameCreated(browser, frame);
return;
}
NavigateOrderMainTestHandler::OnFrameCreated(browser, frame);
}
void OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
bool reattached) override {
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnFrameAttached(browser, frame);
return;
}
NavigateOrderMainTestHandler::OnFrameAttached(browser, frame, reattached);
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (!frame->IsMain()) {
// Potentially the last notification for an old sub-frame after
// navigation.
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnFrameDetached(browser, frame);
if (map->AllFramesDetached()) {
// Verify results from the previous navigation.
VerifyAndClearSubFrameTestResults(map);
}
return;
}
NavigateOrderMainTestHandler::OnFrameDetached(browser, frame);
}
void OnLoadStart(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
TransitionType transition_type) override {
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnLoadStart(browser, frame);
return;
}
NavigateOrderMainTestHandler::OnLoadStart(browser, frame, transition_type);
}
void OnLoadEnd(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int httpStatusCode) override {
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
status->OnLoadEnd(browser, frame);
return;
}
NavigateOrderMainTestHandler::OnLoadEnd(browser, frame, httpStatusCode);
}
protected:
virtual std::string GetSubURL1ForNav(int nav) const {
return GetURLForNav(nav, "sub1");
}
std::string GetSubFrameHtml1ForNav(int nav) const {
if (test_mode_ == SUBFRAME_CHILDREN) {
return "<iframe src=\"" + GetSubURL2ForNav(nav) + "\">";
}
return "<html><body>Sub1</body></html>";
}
virtual std::string GetSubURL2ForNav(int nav) const {
return GetURLForNav(nav, "sub2");
}
std::string GetSubFrameHtml2ForNav(int nav) const {
return "<html><body>Sub2</body></html>";
}
std::string GetMainHtmlForNav(int nav) const override {
if (test_mode_ == SUBFRAME_PEERS) {
return "<html><body><iframe src=\"" + GetSubURL1ForNav(nav) +
"\"></iframe><iframe src=\"" + GetSubURL2ForNav(nav) +
"\"></iframe></body></html>";
} else if (test_mode_ == SUBFRAME_CHILDREN) {
return "<html><body><iframe src=\"" + GetSubURL1ForNav(nav) +
"\"></iframe></iframe></body></html>";
}
NOTREACHED();
return std::string();
}
bool AllQueriesDelivered(std::string* msg = nullptr) const override {
if (!NavigateOrderMainTestHandler::AllQueriesDelivered(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " MAIN PENDING";
}
#endif
return false;
}
if (frame_maps_.empty()) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " NO SUBS";
}
#endif
return false;
}
if (!frame_maps_.back()->AllQueriesDelivered(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " SUBS PENDING";
}
#endif
return false;
}
return true;
}
bool AllFramesLoaded(std::string* msg = nullptr) const override {
if (!NavigateOrderMainTestHandler::AllFramesLoaded(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " MAIN PENDING";
}
#endif
return false;
}
if (frame_maps_.empty()) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " NO SUBS";
}
#endif
return false;
}
if (!frame_maps_.back()->AllFramesLoaded(msg)) {
#if VERBOSE_DEBUGGING
if (msg) {
*msg += " SUBS PENDING";
}
#endif
return false;
}
return true;
}
void VerifyTestResults() override {
NavigateOrderMainTestHandler::VerifyTestResults();
EXPECT_TRUE(frame_maps_.empty());
}
size_t expected_frame_ct() const { return expected_frame_ct_; }
FrameStatusMap* GetFrameMap(CefRefPtr<CefFrame> frame) const {
for (auto& map : frame_maps_) {
if (map->Contains(frame)) {
return map.get();
}
}
return nullptr;
}
private:
// All sub-frame objects should already have received all callbacks.
void VerifyAndClearSubFrameTestResults(FrameStatusMap* map) {
map->VerifyAndClearTestResults();
bool found = false;
FrameStatusMapVector::iterator it = frame_maps_.begin();
for (; it != frame_maps_.end(); ++it) {
if ((*it).get() == map) {
frame_maps_.erase(it);
found = true;
break;
}
}
EXPECT_TRUE(found);
}
FrameStatusMap* GetOrCreateFrameMap(CefRefPtr<CefFrame> frame) {
if (auto map = GetFrameMap(frame)) {
return map;
}
if (frame_maps_.empty() ||
frame_maps_.back()->size() >= expected_frame_ct_) {
// Start a new frame map.
auto map = std::make_unique<FrameStatusMap>(expected_frame_ct_);
frame_maps_.push_back(std::move(map));
}
return frame_maps_.back().get();
}
const TestMode test_mode_;
// The expected number of sub-frames.
const size_t expected_frame_ct_;
using FrameStatusMapVector = std::vector<std::unique_ptr<FrameStatusMap>>;
FrameStatusMapVector frame_maps_;
};
} // namespace
// Main frame loads two sub-frames that are peers in the same origin.
TEST(FrameHandlerTest, OrderSubSameOriginPeers) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/false, /*additional_nav_ct=*/0,
OrderSubTestHandler::SUBFRAME_PEERS);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads two sub-frames that are peers in the same origin, then
// navigates in the same origin and does it again twice.
TEST(FrameHandlerTest, OrderSubSameOriginPeersNavSameOrigin) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/false, /*additional_nav_ct=*/2,
OrderSubTestHandler::SUBFRAME_PEERS);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads two sub-frames that are peers in the same origin, then
// navigates cross-origin and does it again twice.
TEST(FrameHandlerTest, OrderSubSameOriginPeersNavCrossOrigin) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/true, /*additional_nav_ct=*/2,
OrderSubTestHandler::SUBFRAME_PEERS);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads a sub-frame that then has it's own sub-frame.
TEST(FrameHandlerTest, OrderSubSameOriginChildren) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/false, /*additional_nav_ct=*/0,
OrderSubTestHandler::SUBFRAME_CHILDREN);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads a sub-frame that then has it's own sub-frame, then navigates
// in the same origin and does it again twice.
TEST(FrameHandlerTest, OrderSubSameOriginChildrenNavSameOrigin) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/false, /*additional_nav_ct=*/2,
OrderSubTestHandler::SUBFRAME_CHILDREN);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads a sub-frame that then has it's own sub-frame, then navigates
// cross-origin and does it again twice.
TEST(FrameHandlerTest, OrderSubSameOriginChildrenNavCrossOrigin) {
CefRefPtr<OrderSubTestHandler> handler =
new OrderSubTestHandler(/*cross_origin=*/true, /*additional_nav_ct=*/2,
OrderSubTestHandler::SUBFRAME_CHILDREN);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
// Like above, but also navigating the sub-frames cross-origin.
class CrossOriginOrderSubTestHandler : public OrderSubTestHandler {
public:
CrossOriginOrderSubTestHandler(int additional_nav_ct, TestMode mode)
: OrderSubTestHandler(/*cross_origin=*/true,
additional_nav_ct,
mode,
/*expected_frame_ct=*/4U) {}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
// A sub-frame is first created in the parent's renderer process. That
// sub-frame is then discarded after the real cross-origin sub-frame is
// created in a different renderer process. These discarded sub-frames will
// get OnFrameCreated/OnFrameAttached immediately followed by
// OnFrameDetached.
if (!frame->IsMain()) {
auto map = GetFrameMap(frame);
auto status = map->GetFrameStatus(frame);
if (status && !status->DidGetCallback(FrameStatus::LOAD_START)) {
status->SetIsTemporary(true);
temp_frame_detached_ct_++;
}
}
OrderSubTestHandler::OnFrameDetached(browser, frame);
}
protected:
std::string GetSubURL1ForNav(int nav) const override {
std::stringstream ss;
ss << kOrderMainUrlPrefix << nav << "-sub1/sub-cross-origin.html";
return ss.str();
}
std::string GetSubURL2ForNav(int nav) const override {
std::stringstream ss;
ss << kOrderMainUrlPrefix << nav << "-sub2/sub-cross-origin.html";
return ss.str();
}
void VerifyTestResults() override {
OrderSubTestHandler::VerifyTestResults();
const size_t expected_temp_ct =
(expected_frame_ct() / 2U) * (1U + additional_nav_ct());
EXPECT_EQ(expected_temp_ct, temp_frame_detached_ct_);
}
private:
size_t temp_frame_detached_ct_ = 0U;
};
} // namespace
// Main frame loads two sub-frames that are peers in a different origin.
TEST(FrameHandlerTest, OrderSubCrossOriginPeers) {
CefRefPtr<CrossOriginOrderSubTestHandler> handler =
new CrossOriginOrderSubTestHandler(
/*additional_nav_ct=*/0,
CrossOriginOrderSubTestHandler::SUBFRAME_PEERS);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads two sub-frames that are peers in a different origin, then
// navigates cross-origin and does it again twice.
TEST(FrameHandlerTest, OrderSubCrossOriginPeersNavCrossOrigin) {
CefRefPtr<CrossOriginOrderSubTestHandler> handler =
new CrossOriginOrderSubTestHandler(
/*additional_nav_ct=*/2,
CrossOriginOrderSubTestHandler::SUBFRAME_PEERS);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads a sub-frame in a different origin that then has it's own
// sub-frame in a different origin.
TEST(FrameHandlerTest, OrderSubCrossOriginChildren) {
CefRefPtr<CrossOriginOrderSubTestHandler> handler =
new CrossOriginOrderSubTestHandler(
/*additional_nav_ct=*/0,
CrossOriginOrderSubTestHandler::SUBFRAME_CHILDREN);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
// Main frame loads a sub-frame in a different origin that then has it's own
// sub-frame in a different origin, then navigates cross-origin and does it
// again twice.
TEST(FrameHandlerTest, OrderSubCrossOriginChildrenNavCrossOrigin) {
CefRefPtr<CrossOriginOrderSubTestHandler> handler =
new CrossOriginOrderSubTestHandler(
/*additional_nav_ct=*/2,
CrossOriginOrderSubTestHandler::SUBFRAME_CHILDREN);
handler->ExecuteTest();
ReleaseAndWaitForDestructor(handler);
}
namespace {
const char kOrderMainCrossUrl[] =
"https://tests-frame-handler-cross/main-order.html";
// Will be assigned as popup handler via
// ParentOrderMainTestHandler::OnBeforePopup.
class PopupOrderMainTestHandler : public OrderMainTestHandler {
public:
PopupOrderMainTestHandler(CompletionState* completion_state,
bool cross_origin)
: OrderMainTestHandler(completion_state), cross_origin_(cross_origin) {
expected_main_frame_changed_ct_ = cross_origin_ ? 1 : 0;
}
void SetupTest() override {
// Proceed to RunTest().
SetupComplete();
}
void RunTest() override {
// Add the main resource that we will navigate to/from.
AddResource(GetMainURL(), GetMainHtml(), "text/html");
// Time out the test after a reasonable period of time.
SetTestTimeout();
}
void OnFrameCreated(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
EXPECT_UI_THREAD();
EXPECT_TRUE(frame->IsMain());
if (cross_origin_ && !temp_main_frame_) {
// The first main frame in the popup will be created in the parent
// process.
EXPECT_FALSE(got_temp_created_);
got_temp_created_.yes();
temp_main_frame_ = new FrameStatus(frame);
temp_main_frame_->SetAdditionalDebugInfo(GetAdditionalDebugInfo() +
"temp ");
temp_main_frame_->SetIsFirstMain(true);
temp_main_frame_->OnFrameCreated(browser, frame);
return;
}
OrderMainTestHandler::OnFrameCreated(browser, frame);
}
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(browser->GetMainFrame())) {
EXPECT_FALSE(got_after_created_);
got_after_created_ = true;
EXPECT_TRUE(cross_origin_);
temp_main_frame_->OnAfterCreated(browser);
// Intentionally skipping the immediate parent class method.
RoutingTestHandler::OnAfterCreated(browser);
return;
}
OrderMainTestHandler::OnAfterCreated(browser);
}
void OnFrameAttached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
bool reattached) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(frame)) {
EXPECT_TRUE(cross_origin_);
temp_main_frame_->OnFrameAttached(browser, frame);
return;
}
OrderMainTestHandler::OnFrameAttached(browser, frame, reattached);
}
void OnMainFrameChanged(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> old_frame,
CefRefPtr<CefFrame> new_frame) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(new_frame)) {
EXPECT_TRUE(cross_origin_);
temp_main_frame_->OnMainFrameChanged(browser, old_frame, new_frame);
return;
}
OrderMainTestHandler::OnMainFrameChanged(browser, old_frame, new_frame);
}
void OnFrameDetached(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(frame)) {
EXPECT_TRUE(cross_origin_);
EXPECT_FALSE(got_temp_destroyed_);
got_temp_destroyed_.yes();
#if VERBOSE_DEBUGGING
LOG(INFO) << temp_main_frame_->GetDebugString()
<< " callback OnFrameDetached(discarded)";
#endif
// All of the initial main frame callbacks go to the proxy.
EXPECT_TRUE(temp_main_frame_->DidGetCallback(FrameStatus::AFTER_CREATED));
EXPECT_TRUE(temp_main_frame_->DidGetCallback(
FrameStatus::MAIN_FRAME_INITIAL_ASSIGNED));
EXPECT_TRUE(!temp_main_frame_->DidGetCallback(FrameStatus::LOAD_START));
EXPECT_TRUE(temp_main_frame_->DidGetCallback(FrameStatus::FRAME_CREATED));
EXPECT_TRUE(
temp_main_frame_->DidGetCallback(FrameStatus::FRAME_ATTACHED));
// Should receive queries for OnFrameCreated, OnAfterCreated,
// OnFrameAttached.
EXPECT_EQ(temp_main_frame_->QueriesDeliveredCount(), 3);
delete temp_main_frame_;
temp_main_frame_ = nullptr;
return;
}
OrderMainTestHandler::OnFrameDetached(browser, frame);
}
bool OnQuery(CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int64_t query_id,
const CefString& request,
bool persistent,
CefRefPtr<Callback> callback) override {
if (temp_main_frame_ && temp_main_frame_->IsSame(frame)) {
EXPECT_TRUE(cross_origin_);
temp_main_frame_->OnQuery(browser, frame, request);
return true;
}
return OrderMainTestHandler::OnQuery(browser, frame, query_id, request,
persistent, callback);
}
std::string GetMainURL() const override {
return cross_origin_ ? kOrderMainCrossUrl : kOrderMainUrl;
}
protected:
bool IsCrossOrigin() const override { return cross_origin_; }
void VerifyTestResults() override {
OrderMainTestHandler::VerifyTestResults();
if (cross_origin_) {
EXPECT_TRUE(got_temp_created_);
EXPECT_TRUE(got_temp_destroyed_);
} else {
EXPECT_FALSE(got_temp_created_);
EXPECT_FALSE(got_temp_destroyed_);
}
EXPECT_FALSE(temp_main_frame_);
}
private:
std::string GetAdditionalDebugInfo() const override { return " popup: "; }
const bool cross_origin_;
TrackCallback got_temp_created_;
TrackCallback got_temp_destroyed_;
FrameStatus* temp_main_frame_ = nullptr;
};
class ParentOrderMainTestHandler : public OrderMainTestHandler {
public:
ParentOrderMainTestHandler(CompletionState* completion_state,
CefRefPtr<PopupOrderMainTestHandler> popup_handler)
: OrderMainTestHandler(completion_state), popup_handler_(popup_handler) {}
bool OnBeforePopup(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
int popup_id,
const CefString& target_url,
const CefString& target_frame_name,
CefLifeSpanHandler::WindowOpenDisposition target_disposition,
bool user_gesture,
const CefPopupFeatures& popupFeatures,
CefWindowInfo& windowInfo,
CefRefPtr<CefClient>& client,
CefBrowserSettings& settings,
CefRefPtr<CefDictionaryValue>& extra_info,
bool* no_javascript_access) override {
// Intentionally not calling the parent class method.
EXPECT_FALSE(got_on_before_popup_);
got_on_before_popup_.yes();
client = popup_handler_;
popup_handler_ = nullptr;
// Proceed with popup creation.
return false;
}
void OnAfterCreated(CefRefPtr<CefBrowser> browser) override {
OrderMainTestHandler::OnAfterCreated(browser);
GrantPopupPermission(browser->GetHost()->GetRequestContext(), GetMainURL());
// Create the popup ASAP.
browser->GetMainFrame()->ExecuteJavaScript(
"window.open('" + popup_handler_->GetMainURL() + "');", CefString(), 0);
}
void SetupTest() override {
// Proceed to RunTest().
SetupComplete();
}
void DestroyTest() override {
EXPECT_TRUE(got_on_before_popup_);
OrderMainTestHandler::DestroyTest();
}
private:
std::string GetAdditionalDebugInfo() const override { return "parent: "; }
CefRefPtr<PopupOrderMainTestHandler> popup_handler_;
TrackCallback got_on_before_popup_;
};
void RunOrderMainPopupTest(bool cross_origin) {
TestHandler::CompletionState completion_state(/*total=*/2);
TestHandler::Collection collection(&completion_state);
CefRefPtr<PopupOrderMainTestHandler> popup_handler =
new PopupOrderMainTestHandler(&completion_state, cross_origin);
CefRefPtr<ParentOrderMainTestHandler> parent_handler =
new ParentOrderMainTestHandler(&completion_state, popup_handler);
collection.AddTestHandler(popup_handler.get());
collection.AddTestHandler(parent_handler.get());
collection.ExecuteTests();
ReleaseAndWaitForDestructor(parent_handler);
ReleaseAndWaitForDestructor(popup_handler);
}
} // namespace
// Test the ordering and behavior of main frame callbacks in a popup with the
// same origin.
TEST(FrameHandlerTest, OrderMainPopupSameOrigin) {
RunOrderMainPopupTest(/*cross_origin=*/false);
}
// Test the ordering and behavior of main frame callbacks in a popup with a
// different origin.
TEST(FrameHandlerTest, OrderMainPopupCrossOrigin) {
RunOrderMainPopupTest(/*cross_origin=*/true);
}