1741 lines
54 KiB
C++
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);
|
|
}
|