cef/libcef/browser/frame_host_impl.cc
Marshall Greenblatt 3d1bbaf54f Support reconnect of the mojo frame channel (fixes issue #3260)
The mojo channel used for frame communication may disconnect for a variety of
reasons including frame navigation, frame destruction, or insertion into the
BackForwardCache (when the browser-side frame representation is destroyed and
closes the connection). When disconnect occurs we now evaluate the situation
and reconnect if appropriate.

Connections are now initiated solely from the renderer process and the
RenderFrame is passed as an argument to FrameAttached() instead of being
retrieved independently. Messages are queued while the frame is disconnected
and sent only after FrameAttachedAck() is received from the browser process.
The renderer process will be crashed intentionally with a "connection retry
failure" message if the reconnect fails 3 times in a row.
2022-02-14 20:31:04 -05:00

649 lines
20 KiB
C++

// Copyright (c) 2012 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 "libcef/browser/frame_host_impl.h"
#include "include/cef_request.h"
#include "include/cef_stream.h"
#include "include/cef_v8.h"
#include "include/test/cef_test_helpers.h"
#include "libcef/browser/browser_host_base.h"
#include "libcef/browser/net_service/browser_urlrequest_impl.h"
#include "libcef/common/frame_util.h"
#include "libcef/common/net/url_util.h"
#include "libcef/common/process_message_impl.h"
#include "libcef/common/request_impl.h"
#include "libcef/common/string_util.h"
#include "libcef/common/task_runner_impl.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
#include "services/service_manager/public/cpp/interface_provider.h"
namespace {
void StringVisitCallback(CefRefPtr<CefStringVisitor> visitor,
base::ReadOnlySharedMemoryRegion response) {
string_util::ExecuteWithScopedCefString(
std::move(response),
base::BindOnce([](CefRefPtr<CefStringVisitor> visitor,
const CefString& str) { visitor->Visit(str); },
visitor));
}
void ViewTextCallback(CefRefPtr<CefFrameHostImpl> frame,
base::ReadOnlySharedMemoryRegion response) {
if (auto browser = frame->GetBrowser()) {
string_util::ExecuteWithScopedCefString(
std::move(response),
base::BindOnce(
[](CefRefPtr<CefBrowser> browser, const CefString& str) {
static_cast<CefBrowserHostBase*>(browser.get())->ViewText(str);
},
browser));
}
}
} // namespace
CefFrameHostImpl::CefFrameHostImpl(scoped_refptr<CefBrowserInfo> browser_info,
int64_t parent_frame_id)
: is_main_frame_(false),
frame_id_(kInvalidFrameId),
browser_info_(browser_info),
is_focused_(is_main_frame_), // The main frame always starts focused.
parent_frame_id_(parent_frame_id) {
#if DCHECK_IS_ON()
DCHECK(browser_info_);
if (is_main_frame_) {
DCHECK_EQ(parent_frame_id_, kInvalidFrameId);
} else {
DCHECK_GT(parent_frame_id_, 0);
}
#endif
}
CefFrameHostImpl::CefFrameHostImpl(scoped_refptr<CefBrowserInfo> browser_info,
content::RenderFrameHost* render_frame_host)
: is_main_frame_(render_frame_host->GetParent() == nullptr),
frame_id_(frame_util::MakeFrameId(render_frame_host->GetGlobalId())),
browser_info_(browser_info),
is_focused_(is_main_frame_), // The main frame always starts focused.
url_(render_frame_host->GetLastCommittedURL().spec()),
name_(render_frame_host->GetFrameName()),
parent_frame_id_(
is_main_frame_ ? kInvalidFrameId
: frame_util::MakeFrameId(
render_frame_host->GetParent()->GetGlobalId())),
render_frame_host_(render_frame_host) {
DCHECK(browser_info_);
}
CefFrameHostImpl::~CefFrameHostImpl() {
// Should have been Detached if not temporary.
DCHECK(is_temporary() || !browser_info_);
DCHECK(!render_frame_host_);
}
bool CefFrameHostImpl::IsValid() {
return !!GetBrowserHostBase();
}
void CefFrameHostImpl::Undo() {
SendCommand("Undo");
}
void CefFrameHostImpl::Redo() {
SendCommand("Redo");
}
void CefFrameHostImpl::Cut() {
SendCommand("Cut");
}
void CefFrameHostImpl::Copy() {
SendCommand("Copy");
}
void CefFrameHostImpl::Paste() {
SendCommand("Paste");
}
void CefFrameHostImpl::Delete() {
SendCommand("Delete");
}
void CefFrameHostImpl::SelectAll() {
SendCommand("SelectAll");
}
void CefFrameHostImpl::ViewSource() {
SendCommandWithResponse(
"GetSource",
base::BindOnce(&ViewTextCallback, CefRefPtr<CefFrameHostImpl>(this)));
}
void CefFrameHostImpl::GetSource(CefRefPtr<CefStringVisitor> visitor) {
SendCommandWithResponse("GetSource",
base::BindOnce(&StringVisitCallback, visitor));
}
void CefFrameHostImpl::GetText(CefRefPtr<CefStringVisitor> visitor) {
SendCommandWithResponse("GetText",
base::BindOnce(&StringVisitCallback, visitor));
}
void CefFrameHostImpl::LoadRequest(CefRefPtr<CefRequest> request) {
auto params = cef::mojom::RequestParams::New();
static_cast<CefRequestImpl*>(request.get())->Get(params);
LoadRequest(std::move(params));
}
void CefFrameHostImpl::LoadURL(const CefString& url) {
LoadURLWithExtras(url, content::Referrer(), kPageTransitionExplicit,
std::string());
}
void CefFrameHostImpl::ExecuteJavaScript(const CefString& jsCode,
const CefString& scriptUrl,
int startLine) {
SendJavaScript(jsCode, scriptUrl, startLine);
}
bool CefFrameHostImpl::IsMain() {
return is_main_frame_;
}
bool CefFrameHostImpl::IsFocused() {
base::AutoLock lock_scope(state_lock_);
return is_focused_;
}
CefString CefFrameHostImpl::GetName() {
base::AutoLock lock_scope(state_lock_);
return name_;
}
int64 CefFrameHostImpl::GetIdentifier() {
base::AutoLock lock_scope(state_lock_);
return frame_id_;
}
CefRefPtr<CefFrame> CefFrameHostImpl::GetParent() {
int64 parent_frame_id;
{
base::AutoLock lock_scope(state_lock_);
if (is_main_frame_ || parent_frame_id_ == kInvalidFrameId)
return nullptr;
parent_frame_id = parent_frame_id_;
}
auto browser = GetBrowserHostBase();
if (browser)
return browser->GetFrame(parent_frame_id);
return nullptr;
}
CefString CefFrameHostImpl::GetURL() {
base::AutoLock lock_scope(state_lock_);
return url_;
}
CefRefPtr<CefBrowser> CefFrameHostImpl::GetBrowser() {
return GetBrowserHostBase().get();
}
CefRefPtr<CefV8Context> CefFrameHostImpl::GetV8Context() {
NOTREACHED() << "GetV8Context cannot be called from the browser process";
return nullptr;
}
void CefFrameHostImpl::VisitDOM(CefRefPtr<CefDOMVisitor> visitor) {
NOTREACHED() << "VisitDOM cannot be called from the browser process";
}
CefRefPtr<CefURLRequest> CefFrameHostImpl::CreateURLRequest(
CefRefPtr<CefRequest> request,
CefRefPtr<CefURLRequestClient> client) {
if (!request || !client)
return nullptr;
if (!CefTaskRunnerImpl::GetCurrentTaskRunner()) {
NOTREACHED() << "called on invalid thread";
return nullptr;
}
auto browser = GetBrowserHostBase();
if (!browser)
return nullptr;
auto request_context = browser->request_context();
CefRefPtr<CefBrowserURLRequest> impl =
new CefBrowserURLRequest(this, request, client, request_context);
if (impl->Start())
return impl.get();
return nullptr;
}
void CefFrameHostImpl::SendProcessMessage(
CefProcessId target_process,
CefRefPtr<CefProcessMessage> message) {
DCHECK_EQ(PID_RENDERER, target_process);
DCHECK(message && message->IsValid());
if (!message || !message->IsValid())
return;
// Invalidate the message object immediately by taking the argument list.
auto argument_list =
static_cast<CefProcessMessageImpl*>(message.get())->TakeArgumentList();
SendToRenderFrame(__FUNCTION__,
base::BindOnce(
[](const CefString& name, base::ListValue argument_list,
const RenderFrameType& render_frame) {
render_frame->SendMessage(name,
std::move(argument_list));
},
message->GetName(), std::move(argument_list)));
}
void CefFrameHostImpl::SetFocused(bool focused) {
base::AutoLock lock_scope(state_lock_);
is_focused_ = focused;
}
void CefFrameHostImpl::RefreshAttributes() {
CEF_REQUIRE_UIT();
base::AutoLock lock_scope(state_lock_);
if (!render_frame_host_)
return;
url_ = render_frame_host_->GetLastCommittedURL().spec();
// Use the assigned name if it is non-empty. This represents the name property
// on the frame DOM element. If the assigned name is empty, revert to the
// internal unique name. This matches the logic in render_frame_util::GetName.
name_ = render_frame_host_->GetFrameName();
if (name_.empty()) {
const auto node = content::FrameTreeNode::GloballyFindByID(
render_frame_host_->GetFrameTreeNodeId());
if (node) {
name_ = node->unique_name();
}
}
if (!is_main_frame_) {
parent_frame_id_ =
frame_util::MakeFrameId(render_frame_host_->GetParent()->GetGlobalId());
}
}
void CefFrameHostImpl::NotifyMoveOrResizeStarted() {
SendToRenderFrame(__FUNCTION__,
base::BindOnce([](const RenderFrameType& render_frame) {
render_frame->MoveOrResizeStarted();
}));
}
void CefFrameHostImpl::LoadRequest(cef::mojom::RequestParamsPtr params) {
if (!url_util::FixupGURL(params->url))
return;
SendToRenderFrame(__FUNCTION__,
base::BindOnce(
[](cef::mojom::RequestParamsPtr params,
const RenderFrameType& render_frame) {
render_frame->LoadRequest(std::move(params));
},
std::move(params)));
auto browser = GetBrowserHostBase();
if (browser)
browser->OnSetFocus(FOCUS_SOURCE_NAVIGATION);
}
void CefFrameHostImpl::LoadURLWithExtras(const std::string& url,
const content::Referrer& referrer,
ui::PageTransition transition,
const std::string& extra_headers) {
// Only known frame ids or kMainFrameId are supported.
const auto frame_id = GetFrameId();
if (frame_id < CefFrameHostImpl::kMainFrameId)
return;
// Any necessary fixup will occur in LoadRequest.
GURL gurl = url_util::MakeGURL(url, /*fixup=*/false);
if (frame_id == CefFrameHostImpl::kMainFrameId) {
// Load via the browser using NavigationController.
auto browser = GetBrowserHostBase();
if (browser) {
content::OpenURLParams params(
gurl, referrer, WindowOpenDisposition::CURRENT_TAB, transition,
/*is_renderer_initiated=*/false);
params.extra_headers = extra_headers;
browser->LoadMainFrameURL(params);
}
} else {
auto params = cef::mojom::RequestParams::New();
params->url = gurl;
params->referrer =
blink::mojom::Referrer::New(referrer.url, referrer.policy);
params->headers = extra_headers;
LoadRequest(std::move(params));
}
}
void CefFrameHostImpl::SendCommand(const std::string& command) {
DCHECK(!command.empty());
SendToRenderFrame(__FUNCTION__, base::BindOnce(
[](const std::string& command,
const RenderFrameType& render_frame) {
render_frame->SendCommand(command);
},
command));
}
void CefFrameHostImpl::SendCommandWithResponse(
const std::string& command,
cef::mojom::RenderFrame::SendCommandWithResponseCallback
response_callback) {
DCHECK(!command.empty());
SendToRenderFrame(
__FUNCTION__,
base::BindOnce(
[](const std::string& command,
cef::mojom::RenderFrame::SendCommandWithResponseCallback
response_callback,
const RenderFrameType& render_frame) {
render_frame->SendCommandWithResponse(command,
std::move(response_callback));
},
command, std::move(response_callback)));
}
void CefFrameHostImpl::SendJavaScript(const std::u16string& jsCode,
const std::string& scriptUrl,
int startLine) {
if (jsCode.empty())
return;
if (startLine <= 0) {
// A value of 0 is v8::Message::kNoLineNumberInfo in V8. There is code in
// V8 that will assert on that value (e.g. V8StackTraceImpl::Frame::Frame
// if a JS exception is thrown) so make sure |startLine| > 0.
startLine = 1;
}
SendToRenderFrame(
__FUNCTION__,
base::BindOnce(
[](const std::u16string& jsCode, const std::string& scriptUrl,
int startLine, const RenderFrameType& render_frame) {
render_frame->SendJavaScript(jsCode, scriptUrl, startLine);
},
jsCode, scriptUrl, startLine));
}
void CefFrameHostImpl::MaybeSendDidStopLoading() {
auto rfh = GetRenderFrameHost();
if (!rfh)
return;
// We only want to notify for the highest-level LocalFrame in this frame's
// renderer process subtree. If this frame has a parent in the same process
// then the notification will be sent via the parent instead.
auto rfh_parent = rfh->GetParent();
if (rfh_parent && rfh_parent->GetProcess() == rfh->GetProcess()) {
return;
}
SendToRenderFrame(__FUNCTION__,
base::BindOnce([](const RenderFrameType& render_frame) {
render_frame->DidStopLoading();
}));
}
void CefFrameHostImpl::ExecuteJavaScriptWithUserGestureForTests(
const CefString& javascript) {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(
CEF_UIT,
base::BindOnce(
&CefFrameHostImpl::ExecuteJavaScriptWithUserGestureForTests, this,
javascript));
return;
}
content::RenderFrameHost* rfh = GetRenderFrameHost();
if (rfh)
rfh->ExecuteJavaScriptWithUserGestureForTests(javascript);
}
content::RenderFrameHost* CefFrameHostImpl::GetRenderFrameHost() const {
CEF_REQUIRE_UIT();
return render_frame_host_;
}
bool CefFrameHostImpl::Detach() {
CEF_REQUIRE_UIT();
// May be called multiple times (e.g. from CefBrowserInfo SetMainFrame and
// RemoveFrame).
bool first_detach = false;
// Should not be called for temporary frames.
DCHECK(!is_temporary());
{
base::AutoLock lock_scope(state_lock_);
if (browser_info_) {
first_detach = true;
browser_info_ = nullptr;
}
}
// In case we never attached, clean up.
while (!queued_renderer_actions_.empty()) {
queued_renderer_actions_.pop();
}
render_frame_.reset();
render_frame_host_ = nullptr;
return first_detach;
}
void CefFrameHostImpl::MaybeReAttach(
scoped_refptr<CefBrowserInfo> browser_info,
content::RenderFrameHost* render_frame_host) {
CEF_REQUIRE_UIT();
if (render_frame_.is_bound() && render_frame_host_ == render_frame_host) {
// Nothing to do here.
return;
}
// We expect that Detach() was called previously.
CHECK(!is_temporary());
CHECK(!render_frame_.is_bound());
CHECK(!render_frame_host_);
// The RFH may change but the GlobalId should remain the same.
CHECK_EQ(frame_id_,
frame_util::MakeFrameId(render_frame_host->GetGlobalId()));
{
base::AutoLock lock_scope(state_lock_);
browser_info_ = browser_info;
}
render_frame_host_ = render_frame_host;
RefreshAttributes();
// We expect a reconnect to be triggered via FrameAttached().
}
// kMainFrameId must be -1 to align with renderer expectations.
const int64_t CefFrameHostImpl::kMainFrameId = -1;
const int64_t CefFrameHostImpl::kFocusedFrameId = -2;
const int64_t CefFrameHostImpl::kUnspecifiedFrameId = -3;
const int64_t CefFrameHostImpl::kInvalidFrameId = -4;
// This equates to (TT_EXPLICIT | TT_DIRECT_LOAD_FLAG).
const ui::PageTransition CefFrameHostImpl::kPageTransitionExplicit =
static_cast<ui::PageTransition>(ui::PAGE_TRANSITION_TYPED |
ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
int64 CefFrameHostImpl::GetFrameId() const {
base::AutoLock lock_scope(state_lock_);
return is_main_frame_ ? kMainFrameId : frame_id_;
}
scoped_refptr<CefBrowserInfo> CefFrameHostImpl::GetBrowserInfo() const {
base::AutoLock lock_scope(state_lock_);
return browser_info_;
}
CefRefPtr<CefBrowserHostBase> CefFrameHostImpl::GetBrowserHostBase() const {
if (auto browser_info = GetBrowserInfo())
return browser_info->browser();
return nullptr;
}
void CefFrameHostImpl::SendToRenderFrame(const std::string& function_name,
RenderFrameAction action) {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(&CefFrameHostImpl::SendToRenderFrame, this,
function_name, std::move(action)));
return;
}
if (is_temporary()) {
LOG(WARNING) << function_name
<< " sent to temporary subframe will be ignored.";
return;
} else if (!render_frame_host_) {
// We've been detached.
LOG(WARNING) << function_name << " sent to detached "
<< (is_main_frame_ ? "main" : "sub") << "frame "
<< frame_util::GetFrameDebugString(frame_id_)
<< " will be ignored";
return;
}
if (!render_frame_.is_bound()) {
// Queue actions until we're notified by the renderer that it's ready to
// handle them.
queued_renderer_actions_.push(
std::make_pair(function_name, std::move(action)));
return;
}
std::move(action).Run(render_frame_);
}
void CefFrameHostImpl::OnRenderFrameDisconnect() {
CEF_REQUIRE_UIT();
// Reconnect, if any, will be triggered via FrameAttached().
render_frame_.reset();
}
void CefFrameHostImpl::SendMessage(const std::string& name,
base::Value arguments) {
if (auto browser = GetBrowserHostBase()) {
if (auto client = browser->GetClient()) {
auto& list_value = base::Value::AsListValue(arguments);
CefRefPtr<CefProcessMessageImpl> message(new CefProcessMessageImpl(
name, std::move(const_cast<base::ListValue&>(list_value)),
/*read_only=*/true));
browser->GetClient()->OnProcessMessageReceived(
browser.get(), this, PID_RENDERER, message.get());
}
}
}
void CefFrameHostImpl::FrameAttached(
mojo::PendingRemote<cef::mojom::RenderFrame> render_frame_remote,
bool reattached) {
CEF_REQUIRE_UIT();
CHECK(render_frame_remote);
auto browser_info = GetBrowserInfo();
if (!browser_info) {
// Already Detached.
return;
}
if (reattached) {
LOG(INFO) << (is_main_frame_ ? "main" : "sub") << "frame "
<< frame_util::GetFrameDebugString(frame_id_)
<< " has reconnected";
}
render_frame_.Bind(std::move(render_frame_remote));
render_frame_.set_disconnect_handler(
base::BindOnce(&CefFrameHostImpl::OnRenderFrameDisconnect, this));
// Notify the renderer process that it can start sending messages.
render_frame_->FrameAttachedAck();
while (!queued_renderer_actions_.empty()) {
std::move(queued_renderer_actions_.front().second).Run(render_frame_);
queued_renderer_actions_.pop();
}
browser_info->MaybeExecuteFrameNotification(base::BindOnce(
[](CefRefPtr<CefFrameHostImpl> self, bool reattached,
CefRefPtr<CefFrameHandler> handler) {
if (auto browser = self->GetBrowserHostBase()) {
handler->OnFrameAttached(browser, self, reattached);
}
},
CefRefPtr<CefFrameHostImpl>(this), reattached));
}
void CefFrameHostImpl::DidFinishFrameLoad(const GURL& validated_url,
int http_status_code) {
auto browser = GetBrowserHostBase();
if (browser)
browser->OnDidFinishLoad(this, validated_url, http_status_code);
}
void CefFrameHostImpl::UpdateDraggableRegions(
absl::optional<std::vector<cef::mojom::DraggableRegionEntryPtr>> regions) {
auto browser = GetBrowserHostBase();
if (!browser)
return;
std::vector<CefDraggableRegion> draggable_regions;
if (regions) {
draggable_regions.reserve(regions->size());
for (const auto& region : *regions) {
const auto& rect = region->bounds;
const CefRect bounds(rect.x(), rect.y(), rect.width(), rect.height());
draggable_regions.push_back(
CefDraggableRegion(bounds, region->draggable));
}
}
// Delegate to BrowserInfo so that current state is maintained with
// cross-origin navigation.
browser_info_->MaybeNotifyDraggableRegionsChanged(
browser, this, std::move(draggable_regions));
}
void CefExecuteJavaScriptWithUserGestureForTests(CefRefPtr<CefFrame> frame,
const CefString& javascript) {
CefFrameHostImpl* impl = static_cast<CefFrameHostImpl*>(frame.get());
if (impl)
impl->ExecuteJavaScriptWithUserGestureForTests(javascript);
}