| // Copyright 2017 The Chromium 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 "headless/lib/browser/headless_tab_socket_impl.h" |
| |
| #include "base/stl_util.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "services/service_manager/public/cpp/binder_registry.h" |
| #include "services/service_manager/public/cpp/interface_provider.h" |
| |
| namespace headless { |
| |
| HeadlessTabSocketImpl::HeadlessTabSocketImpl(content::WebContents* web_contents) |
| : web_contents_(web_contents), |
| listener_(nullptr), |
| weak_ptr_factory_(this) {} |
| |
| HeadlessTabSocketImpl::~HeadlessTabSocketImpl() {} |
| |
| // Wrangles the async responses to |
| // HeadlessRenderFrameControllerImpl::InstallTabSocket for which at most one |
| // should succeed. |
| class TabSocketInstallationController |
| : public base::RefCounted<TabSocketInstallationController> { |
| public: |
| TabSocketInstallationController( |
| int v8_execution_context_id, |
| size_t render_frame_count, |
| base::WeakPtr<HeadlessTabSocketImpl> headless_tab_socket_impl, |
| base::Callback<void(bool)> callback) |
| : v8_execution_context_id_(v8_execution_context_id), |
| render_frame_count_(render_frame_count), |
| headless_tab_socket_impl_(headless_tab_socket_impl), |
| callback_(callback), |
| success_(false) {} |
| |
| void InstallTabSocketCallback(content::RenderFrameHost* render_frame_host, |
| bool success) { |
| render_frame_count_--; |
| |
| // It's possible the HeadlessTabSocketImpl went away, if that happened we |
| // don't want to pretend TabSocket installation succeeded. |
| if (!headless_tab_socket_impl_) |
| success = false; |
| |
| if (success) { |
| CHECK(!success_) << "At most one InstallTabSocket call should succeed!"; |
| success_ = true; |
| headless_tab_socket_impl_->v8_execution_context_id_to_render_frame_host_ |
| .insert(std::make_pair(v8_execution_context_id_, render_frame_host)); |
| |
| callback_.Run(true); |
| } else if (render_frame_count_ == 0 && !success_) { |
| callback_.Run(false); |
| } |
| } |
| |
| private: |
| int v8_execution_context_id_; |
| size_t render_frame_count_; |
| |
| base::WeakPtr<HeadlessTabSocketImpl> headless_tab_socket_impl_; |
| base::Callback<void(bool)> callback_; |
| bool success_; |
| |
| friend class base::RefCounted<TabSocketInstallationController>; |
| ~TabSocketInstallationController() {} |
| }; |
| |
| void HeadlessTabSocketImpl::InstallHeadlessTabSocketBindings( |
| int v8_execution_context_id, |
| base::Callback<void(bool)> callback) { |
| // We need to find the right RenderFrameHost to install the bindings on but |
| // the browser doesn't know which RenderFrameHost |v8_execution_context_id| |
| // corresponds to if any. So we try all of them. |
| scoped_refptr<TabSocketInstallationController> |
| tab_socket_installation_controller = new TabSocketInstallationController( |
| v8_execution_context_id, render_frame_hosts_.size(), |
| weak_ptr_factory_.GetWeakPtr(), callback); |
| for (content::RenderFrameHost* render_frame_host : render_frame_hosts_) { |
| HeadlessRenderFrameControllerPtr& headless_render_frame_controller = |
| render_frame_controllers_[render_frame_host]; |
| if (!headless_render_frame_controller.is_bound()) { |
| render_frame_host->GetRemoteInterfaces()->GetInterface( |
| &headless_render_frame_controller); |
| } |
| |
| // This will only succeed if the |render_frame_host_controller| contains |
| // |v8_execution_context_id|. The TabSocketInstallationController keeps |
| // track of how many callbacks have been received and if all of them have |
| // been unsuccessful it runs |callback| with false. If one of them succeeds |
| // it runs |callback| with true. |
| headless_render_frame_controller->InstallTabSocket( |
| v8_execution_context_id, |
| base::Bind(&TabSocketInstallationController::InstallTabSocketCallback, |
| tab_socket_installation_controller, render_frame_host)); |
| } |
| } |
| |
| void HeadlessTabSocketImpl::InstallMainFrameMainWorldHeadlessTabSocketBindings( |
| base::Callback<void(base::Optional<int>)> callback) { |
| content::RenderFrameHost* main_frame = web_contents_->GetMainFrame(); |
| HeadlessRenderFrameControllerPtr& headless_render_frame_controller = |
| render_frame_controllers_[main_frame]; |
| if (!headless_render_frame_controller.is_bound()) { |
| main_frame->GetRemoteInterfaces()->GetInterface( |
| &headless_render_frame_controller); |
| } |
| headless_render_frame_controller->InstallMainWorldTabSocket( |
| base::Bind(&HeadlessTabSocketImpl::OnInstallMainWorldTabSocket, |
| weak_ptr_factory_.GetWeakPtr(), callback)); |
| } |
| |
| void HeadlessTabSocketImpl::OnInstallMainWorldTabSocket( |
| base::Callback<void(base::Optional<int>)> callback, |
| int v8_execution_context_id) { |
| if (v8_execution_context_id == -1) { |
| callback.Run(base::nullopt); |
| } else { |
| v8_execution_context_id_to_render_frame_host_.insert( |
| std::make_pair(v8_execution_context_id, web_contents_->GetMainFrame())); |
| callback.Run(v8_execution_context_id); |
| } |
| } |
| |
| void HeadlessTabSocketImpl::SendMessageToContext( |
| const std::string& message, |
| int32_t v8_execution_context_id) { |
| auto render_frame_host = v8_execution_context_id_to_render_frame_host_.find( |
| v8_execution_context_id); |
| if (render_frame_host == |
| v8_execution_context_id_to_render_frame_host_.end()) { |
| LOG(WARNING) << "Unknown v8_execution_context_id " |
| << v8_execution_context_id; |
| return; |
| } |
| |
| auto render_frame_controller = |
| render_frame_controllers_.find(render_frame_host->second); |
| if (render_frame_controller == render_frame_controllers_.end()) { |
| LOG(WARNING) << "Unknown RenderFrameHist " << render_frame_host->second; |
| return; |
| } |
| render_frame_controller->second->SendMessageToTabSocket( |
| message, v8_execution_context_id); |
| } |
| |
| void HeadlessTabSocketImpl::SetListener(Listener* listener) { |
| MessageQueue messages; |
| |
| { |
| base::AutoLock lock(lock_); |
| listener_ = listener; |
| if (!listener) |
| return; |
| |
| std::swap(messages, from_tab_message_queue_); |
| } |
| |
| for (const Message& message : messages) { |
| listener_->OnMessageFromContext(message.first, message.second); |
| } |
| } |
| |
| void HeadlessTabSocketImpl::SendMessageToEmbedder( |
| const std::string& message, |
| int32_t v8_execution_context_id) { |
| Listener* listener = nullptr; |
| { |
| base::AutoLock lock(lock_); |
| CHECK(v8_execution_context_id_to_render_frame_host_.find( |
| v8_execution_context_id) != |
| v8_execution_context_id_to_render_frame_host_.end()) |
| << "Unknown v8_execution_context_id " << v8_execution_context_id; |
| if (listener_) { |
| listener = listener_; |
| } else { |
| from_tab_message_queue_.emplace_back(message, v8_execution_context_id); |
| return; |
| } |
| } |
| |
| listener->OnMessageFromContext(message, v8_execution_context_id); |
| } |
| |
| void HeadlessTabSocketImpl::CreateMojoService( |
| mojo::InterfaceRequest<TabSocket> request) { |
| mojo_bindings_.AddBinding(this, std::move(request)); |
| } |
| |
| void HeadlessTabSocketImpl::RenderFrameCreated( |
| content::RenderFrameHost* render_frame_host) { |
| render_frame_hosts_.insert(render_frame_host); |
| } |
| |
| void HeadlessTabSocketImpl::RenderFrameDeleted( |
| content::RenderFrameHost* render_frame_host) { |
| // Remove all entries from |v8_execution_context_id_to_render_frame_host_| |
| // where the mapped value is |render_frame_host|. |
| base::EraseIf(v8_execution_context_id_to_render_frame_host_, |
| [render_frame_host]( |
| const std::pair<int, content::RenderFrameHost*>& pair) { |
| return render_frame_host == pair.second; |
| }); |
| |
| render_frame_controllers_.erase(render_frame_host); |
| render_frame_hosts_.erase(render_frame_host); |
| } |
| |
| } // namespace headless |