// 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/navigate_params.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/process_message_impl.h" #include "libcef/common/request_impl.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" namespace { // Implementation of CommandResponseHandler for calling a CefStringVisitor. class StringVisitHandler : public CefResponseManager::Handler { public: explicit StringVisitHandler(CefRefPtr visitor) : visitor_(visitor) {} void OnResponse(const Cef_Response_Params& params) override { visitor_->Visit(params.response); } private: CefRefPtr visitor_; IMPLEMENT_REFCOUNTING(StringVisitHandler); }; // Implementation of CommandResponseHandler for calling ViewText(). class ViewTextHandler : public CefResponseManager::Handler { public: explicit ViewTextHandler(CefRefPtr frame) : frame_(frame) {} void OnResponse(const Cef_Response_Params& params) override { CefRefPtr browser = frame_->GetBrowser(); if (browser.get()) { static_cast(browser.get()) ->ViewText(params.response); } } private: CefRefPtr frame_; IMPLEMENT_REFCOUNTING(ViewTextHandler); }; } // namespace CefFrameHostImpl::CefFrameHostImpl(scoped_refptr 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 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 !!GetBrowserHostBase(); } 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 visitor) { SendCommand("GetSource", new StringVisitHandler(visitor)); } void CefFrameHostImpl::GetText(CefRefPtr visitor) { SendCommand("GetText", new StringVisitHandler(visitor)); } void CefFrameHostImpl::LoadRequest(CefRefPtr request) { CefNavigateParams params(GURL(), kPageTransitionExplicit); static_cast(request.get())->Get(params); Navigate(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 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 CefFrameHostImpl::GetBrowser() { return GetBrowserHostBase().get(); } CefRefPtr CefFrameHostImpl::GetV8Context() { NOTREACHED() << "GetV8Context cannot be called from the browser process"; return nullptr; } void CefFrameHostImpl::VisitDOM(CefRefPtr visitor) { NOTREACHED() << "VisitDOM cannot be called from the browser process"; } CefRefPtr CefFrameHostImpl::CreateURLRequest( CefRefPtr request, CefRefPtr 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 impl = new CefBrowserURLRequest(this, request, client, request_context); if (impl->Start()) return impl.get(); return nullptr; } void CefFrameHostImpl::SendProcessMessage( CefProcessId target_process, CefRefPtr message) { DCHECK_EQ(PID_RENDERER, target_process); DCHECK(message.get()); Cef_Request_Params params; CefProcessMessageImpl* impl = static_cast(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: " << 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 = 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; GURL gurl(url); if (!gurl.is_valid() && !gurl.has_scheme()) { // Try to add "http://" at the beginning. std::string new_url = std::string("http://") + url; gurl = GURL(new_url); } if (!gurl.is_valid()) { LOG(ERROR) << "Invalid URL: " << url; return; } 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 { CefNavigateParams params(gurl, transition); params.referrer = referrer; params.headers = extra_headers; Navigate(params); } } void CefFrameHostImpl::SendCommand( const std::string& command, CefRefPtr 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; if (!render_frame_host_ || !response_manager_) { // detached frame has no response_manager_ 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 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; if (!render_frame_host_ || !response_manager_) { // detached frame has no response_manager_ 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_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(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; // This equates to (TT_EXPLICIT | TT_DIRECT_LOAD_FLAG). const ui::PageTransition CefFrameHostImpl::kPageTransitionExplicit = static_cast(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_; } CefRefPtr CefFrameHostImpl::GetBrowserHostBase() 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::OnDidFinishLoad(const GURL& validated_url, int http_status_code) { auto browser = GetBrowserHostBase(); if (browser) browser->OnDidFinishLoad(this, validated_url, http_status_code); } void CefFrameHostImpl::OnUpdateDraggableRegions( const std::vector& regions) { auto browser = GetBrowserHostBase(); if (!browser) return; CefRefPtr handler; auto client = browser->GetClient(); if (client) handler = client->GetDragHandler(); if (!handler) return; std::vector draggable_regions; draggable_regions.reserve(regions.size()); std::vector::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 = GetBrowserHostBase(); if (browser && browser->GetClient()) { // Give the user a chance to handle the request. CefRefPtr message(new CefProcessMessageImpl( const_cast(¶ms), false, true)); success = browser->GetClient()->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 frame, const CefString& javascript) { CefFrameHostImpl* impl = static_cast(frame.get()); if (impl) impl->ExecuteJavaScriptWithUserGestureForTests(javascript); }