cef/libcef/browser/frame_host_impl.cc
Marshall Greenblatt 241941a44a Move message routing from CefBrowser to CefFrame (see issue #2498).
This change moves the SendProcessMessage method from CefBrowser to CefFrame and
adds CefBrowser parameters to OnProcessMessageReceived and
OnDraggableRegionsChanged.

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

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

709 lines
21 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_impl.h"
#include "libcef/browser/navigate_params.h"
#include "libcef/browser/net/browser_urlrequest_old_impl.h"
#include "libcef/browser/net_service/browser_urlrequest_impl.h"
#include "libcef/common/cef_messages.h"
#include "libcef/common/frame_util.h"
#include "libcef/common/net_service/util.h"
#include "libcef/common/process_message_impl.h"
#include "libcef/common/request_impl.h"
#include "libcef/common/task_runner_impl.h"
#include "content/browser/frame_host/frame_tree_node.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_view_host.h"
namespace {
// Implementation of CommandResponseHandler for calling a CefStringVisitor.
class StringVisitHandler : public CefResponseManager::Handler {
public:
explicit StringVisitHandler(CefRefPtr<CefStringVisitor> visitor)
: visitor_(visitor) {}
void OnResponse(const Cef_Response_Params& params) override {
visitor_->Visit(params.response);
}
private:
CefRefPtr<CefStringVisitor> visitor_;
IMPLEMENT_REFCOUNTING(StringVisitHandler);
};
// Implementation of CommandResponseHandler for calling ViewText().
class ViewTextHandler : public CefResponseManager::Handler {
public:
explicit ViewTextHandler(CefRefPtr<CefFrameHostImpl> frame) : frame_(frame) {}
void OnResponse(const Cef_Response_Params& params) override {
CefRefPtr<CefBrowser> browser = frame_->GetBrowser();
if (browser.get()) {
static_cast<CefBrowserHostImpl*>(browser.get())
->ViewText(params.response);
}
}
private:
CefRefPtr<CefFrameHostImpl> frame_;
IMPLEMENT_REFCOUNTING(ViewTextHandler);
};
} // namespace
CefFrameHostImpl::CefFrameHostImpl(scoped_refptr<CefBrowserInfo> browser_info,
bool is_main_frame,
int64_t parent_frame_id)
: is_main_frame_(is_main_frame),
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_(MakeFrameId(render_frame_host)),
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
: MakeFrameId(render_frame_host->GetParent())),
render_frame_host_(render_frame_host),
response_manager_(new CefResponseManager) {
DCHECK(browser_info_);
}
CefFrameHostImpl::~CefFrameHostImpl() {}
void CefFrameHostImpl::SetRenderFrameHost(content::RenderFrameHost* host) {
CEF_REQUIRE_UIT();
base::AutoLock lock_scope(state_lock_);
// We should not be detached.
CHECK(browser_info_);
// We should be the main frame.
CHECK(is_main_frame_);
render_frame_host_ = host;
frame_id_ = MakeFrameId(host);
url_ = host->GetLastCommittedURL().spec();
name_ = host->GetFrameName();
// Cancel any existing messages.
response_manager_.reset(new CefResponseManager);
}
bool CefFrameHostImpl::IsValid() {
return !!GetBrowserHostImpl();
}
void CefFrameHostImpl::Undo() {
SendCommand("Undo", nullptr);
}
void CefFrameHostImpl::Redo() {
SendCommand("Redo", nullptr);
}
void CefFrameHostImpl::Cut() {
SendCommand("Cut", nullptr);
}
void CefFrameHostImpl::Copy() {
SendCommand("Copy", nullptr);
}
void CefFrameHostImpl::Paste() {
SendCommand("Paste", nullptr);
}
void CefFrameHostImpl::Delete() {
SendCommand("Delete", nullptr);
}
void CefFrameHostImpl::SelectAll() {
SendCommand("SelectAll", nullptr);
}
void CefFrameHostImpl::ViewSource() {
SendCommand("GetSource", new ViewTextHandler(this));
}
void CefFrameHostImpl::GetSource(CefRefPtr<CefStringVisitor> visitor) {
SendCommand("GetSource", new StringVisitHandler(visitor));
}
void CefFrameHostImpl::GetText(CefRefPtr<CefStringVisitor> visitor) {
SendCommand("GetText", new StringVisitHandler(visitor));
}
void CefFrameHostImpl::LoadRequest(CefRefPtr<CefRequest> request) {
CefNavigateParams params(GURL(), ui::PAGE_TRANSITION_TYPED);
static_cast<CefRequestImpl*>(request.get())->Get(params);
Navigate(params);
}
void CefFrameHostImpl::LoadURL(const CefString& url) {
LoadURLWithExtras(url, content::Referrer(), ui::PAGE_TRANSITION_TYPED,
std::string());
}
void CefFrameHostImpl::LoadString(const CefString& string,
const CefString& url) {
// Only known frame ids or kMainFrameId are supported.
const auto frame_id = GetFrameId();
if (frame_id < CefFrameHostImpl::kMainFrameId)
return;
Cef_Request_Params params;
params.name = "load-string";
params.user_initiated = false;
params.request_id = -1;
params.expect_response = false;
params.arguments.AppendString(string);
params.arguments.AppendString(url);
Send(new CefMsg_Request(MSG_ROUTING_NONE, params));
}
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 = GetBrowserHostImpl();
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 GetBrowserHostImpl().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 = GetBrowserHostImpl();
if (!browser)
return nullptr;
auto request_context = browser->request_context();
if (net_service::IsEnabled()) {
CefRefPtr<CefBrowserURLRequest> impl =
new CefBrowserURLRequest(this, request, client, request_context);
if (impl->Start())
return impl.get();
} else {
CefRefPtr<CefBrowserURLRequestOld> impl =
new CefBrowserURLRequestOld(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.get());
Cef_Request_Params params;
CefProcessMessageImpl* impl =
static_cast<CefProcessMessageImpl*>(message.get());
if (!impl->CopyTo(params))
return;
DCHECK(!params.name.empty());
params.user_initiated = true;
params.request_id = -1;
params.expect_response = false;
Send(new CefMsg_Request(MSG_ROUTING_NONE, params));
}
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_ = MakeFrameId(render_frame_host_->GetParent());
}
void CefFrameHostImpl::Navigate(const CefNavigateParams& params) {
CefMsg_LoadRequest_Params request;
request.url = params.url;
if (!request.url.is_valid()) {
LOG(ERROR) << "Invalid URL passed to CefFrameHostImpl::Navigate: "
<< params.url;
return;
}
request.method = params.method;
request.referrer = params.referrer.url;
request.referrer_policy =
CefRequestImpl::BlinkReferrerPolicyToNetReferrerPolicy(
params.referrer.policy);
request.site_for_cookies = params.site_for_cookies;
request.headers = params.headers;
request.load_flags = params.load_flags;
request.upload_data = params.upload_data;
Send(new CefMsg_LoadRequest(MSG_ROUTING_NONE, request));
auto browser = GetBrowserHostImpl();
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;
if (frame_id == CefFrameHostImpl::kMainFrameId) {
// Load via the browser using NavigationController.
auto browser = GetBrowserHostImpl();
if (browser) {
browser->LoadMainFrameURL(url, referrer, transition, extra_headers);
}
} else {
CefNavigateParams params(GURL(url), transition);
params.referrer = referrer;
params.headers = extra_headers;
Navigate(params);
}
}
void CefFrameHostImpl::SendCommand(
const std::string& command,
CefRefPtr<CefResponseManager::Handler> responseHandler) {
DCHECK(!command.empty());
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefFrameHostImpl::SendCommand, this,
command, responseHandler));
return;
}
// Only known frame ids or kMainFrameId are supported.
const auto frame_id = GetFrameId();
if (frame_id < CefFrameHostImpl::kMainFrameId)
return;
TRACE_EVENT2("cef", "CefFrameHostImpl::SendCommand", "frame_id", frame_id,
"needsResponse", responseHandler.get() ? 1 : 0);
Cef_Request_Params params;
params.name = "execute-command";
params.user_initiated = false;
if (responseHandler.get()) {
params.request_id = response_manager_->RegisterHandler(responseHandler);
params.expect_response = true;
} else {
params.request_id = -1;
params.expect_response = false;
}
params.arguments.AppendString(command);
Send(new CefMsg_Request(MSG_ROUTING_NONE, params));
}
void CefFrameHostImpl::SendCode(
bool is_javascript,
const std::string& code,
const std::string& script_url,
int script_start_line,
CefRefPtr<CefResponseManager::Handler> responseHandler) {
DCHECK(!code.empty());
DCHECK_GE(script_start_line, 0);
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT, base::BindOnce(&CefFrameHostImpl::SendCode, this,
is_javascript, code, script_url,
script_start_line, responseHandler));
return;
}
// Only known frame ids or kMainFrameId are supported.
auto frame_id = GetFrameId();
if (frame_id < CefFrameHostImpl::kMainFrameId)
return;
TRACE_EVENT2("cef", "CefFrameHostImpl::SendCommand", "frame_id", frame_id,
"needsResponse", responseHandler.get() ? 1 : 0);
Cef_Request_Params params;
params.name = "execute-code";
params.user_initiated = false;
if (responseHandler.get()) {
params.request_id = response_manager_->RegisterHandler(responseHandler);
params.expect_response = true;
} else {
params.request_id = -1;
params.expect_response = false;
}
params.arguments.AppendBoolean(is_javascript);
params.arguments.AppendString(code);
params.arguments.AppendString(script_url);
params.arguments.AppendInteger(script_start_line);
Send(new CefMsg_Request(MSG_ROUTING_NONE, params));
}
void CefFrameHostImpl::SendJavaScript(const std::string& 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;
}
SendCode(true, jsCode, scriptUrl, startLine, nullptr);
}
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;
}
Send(new CefMsg_DidStopLoading(MSG_ROUTING_NONE));
}
bool CefFrameHostImpl::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(CefFrameHostImpl, message)
IPC_MESSAGE_HANDLER(CefHostMsg_FrameAttached, OnAttached)
IPC_MESSAGE_HANDLER(CefHostMsg_FrameFocused, OnFocused)
IPC_MESSAGE_HANDLER(CefHostMsg_DidFinishLoad, OnDidFinishLoad)
IPC_MESSAGE_HANDLER(CefHostMsg_UpdateDraggableRegions,
OnUpdateDraggableRegions)
IPC_MESSAGE_HANDLER(CefHostMsg_Request, OnRequest)
IPC_MESSAGE_HANDLER(CefHostMsg_Response, OnResponse)
IPC_MESSAGE_HANDLER(CefHostMsg_ResponseAck, OnResponseAck)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
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_;
}
void CefFrameHostImpl::Detach() {
CEF_REQUIRE_UIT();
{
base::AutoLock lock_scope(state_lock_);
browser_info_ = nullptr;
}
// In case we never attached, clean up.
while (!queued_messages_.empty()) {
queued_messages_.pop();
}
response_manager_.reset();
render_frame_host_ = nullptr;
}
// static
int64_t CefFrameHostImpl::MakeFrameId(const content::RenderFrameHost* host) {
CEF_REQUIRE_UIT();
auto host_nonconst = const_cast<content::RenderFrameHost*>(host);
return MakeFrameId(host_nonconst->GetProcess()->GetID(),
host_nonconst->GetRoutingID());
}
// static
int64_t CefFrameHostImpl::MakeFrameId(int32_t render_process_id,
int32_t render_routing_id) {
return frame_util::MakeFrameId(render_process_id, render_routing_id);
}
// 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;
int64 CefFrameHostImpl::GetFrameId() const {
base::AutoLock lock_scope(state_lock_);
return is_main_frame_ ? kMainFrameId : frame_id_;
}
CefRefPtr<CefBrowserHostImpl> CefFrameHostImpl::GetBrowserHostImpl() const {
base::AutoLock lock_scope(state_lock_);
if (browser_info_)
return browser_info_->browser();
return nullptr;
}
void CefFrameHostImpl::OnAttached() {
if (!is_attached_) {
is_attached_ = true;
while (!queued_messages_.empty()) {
Send(queued_messages_.front().release());
queued_messages_.pop();
}
}
}
void CefFrameHostImpl::OnFocused() {
if (!IsFocused()) {
// Calls back to SetFocused(true) after updating state on the previously
// focused frame.
auto browser = GetBrowserHostImpl();
if (browser)
browser->OnFrameFocused(this);
}
}
void CefFrameHostImpl::OnDidFinishLoad(const GURL& validated_url,
int http_status_code) {
auto browser = GetBrowserHostImpl();
if (browser)
browser->OnDidFinishLoad(this, validated_url, http_status_code);
}
void CefFrameHostImpl::OnUpdateDraggableRegions(
const std::vector<Cef_DraggableRegion_Params>& regions) {
auto browser = GetBrowserHostImpl();
if (!browser)
return;
CefRefPtr<CefDragHandler> handler;
auto client = browser->GetClient();
if (client)
handler = client->GetDragHandler();
if (!handler)
return;
std::vector<CefDraggableRegion> draggable_regions;
draggable_regions.reserve(regions.size());
std::vector<Cef_DraggableRegion_Params>::const_iterator it = regions.begin();
for (; it != regions.end(); ++it) {
const gfx::Rect& rect(it->bounds);
const CefRect bounds(rect.x(), rect.y(), rect.width(), rect.height());
draggable_regions.push_back(CefDraggableRegion(bounds, it->draggable));
}
handler->OnDraggableRegionsChanged(browser.get(), this, draggable_regions);
}
void CefFrameHostImpl::OnRequest(const Cef_Request_Params& params) {
CEF_REQUIRE_UIT();
bool success = false;
std::string response;
bool expect_response_ack = false;
if (params.user_initiated) {
auto browser = GetBrowserHostImpl();
if (browser && browser->client()) {
// Give the user a chance to handle the request.
CefRefPtr<CefProcessMessageImpl> message(new CefProcessMessageImpl(
const_cast<Cef_Request_Params*>(&params), false, true));
success = browser->client()->OnProcessMessageReceived(
browser.get(), this, PID_RENDERER, message.get());
message->Detach(nullptr);
}
} else {
// Invalid request.
NOTREACHED();
}
if (params.expect_response) {
DCHECK_GE(params.request_id, 0);
// Send a response to the renderer.
Cef_Response_Params response_params;
response_params.request_id = params.request_id;
response_params.success = success;
response_params.response = response;
response_params.expect_response_ack = expect_response_ack;
Send(new CefMsg_Response(MSG_ROUTING_NONE, response_params));
}
}
void CefFrameHostImpl::OnResponse(const Cef_Response_Params& params) {
CEF_REQUIRE_UIT();
response_manager_->RunHandler(params);
if (params.expect_response_ack)
Send(new CefMsg_ResponseAck(MSG_ROUTING_NONE, params.request_id));
}
void CefFrameHostImpl::OnResponseAck(int request_id) {
response_manager_->RunAckHandler(request_id);
}
void CefFrameHostImpl::Send(IPC::Message* message) {
if (!CEF_CURRENTLY_ON_UIT()) {
CEF_POST_TASK(CEF_UIT,
base::BindOnce(base::IgnoreResult(&CefFrameHostImpl::Send),
this, message));
return;
}
if (!render_frame_host_) {
// Either we're a placeholder frame without a renderer representation, or
// we've been detached.
delete message;
return;
}
if (!is_attached_) {
// Queue messages until we're notified by the renderer that it's ready to
// handle them.
queued_messages_.push(base::WrapUnique(message));
return;
}
message->set_routing_id(render_frame_host_->GetRoutingID());
render_frame_host_->Send(message);
}
void CefExecuteJavaScriptWithUserGestureForTests(CefRefPtr<CefFrame> frame,
const CefString& javascript) {
CefFrameHostImpl* impl = static_cast<CefFrameHostImpl*>(frame.get());
if (impl)
impl->ExecuteJavaScriptWithUserGestureForTests(javascript);
}