| // Copyright 2015 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_browser_impl.h" |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/task/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "content/public/app/content_main.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/devtools_agent_host.h" |
| #include "content/public/common/content_switches.h" |
| #include "headless/app/headless_shell_switches.h" |
| #include "headless/lib/browser/headless_browser_context_impl.h" |
| #include "headless/lib/browser/headless_browser_main_parts.h" |
| #include "headless/lib/browser/headless_devtools_agent_host_client.h" |
| #include "headless/lib/browser/headless_web_contents_impl.h" |
| #include "headless/lib/headless_content_main_delegate.h" |
| #include "net/http/http_util.h" |
| #include "services/network/public/cpp/network_switches.h" |
| #include "ui/events/devices/device_data_manager.h" |
| |
| #if defined(USE_NSS_CERTS) |
| #include "net/cert_net/nss_ocsp.h" |
| #endif |
| |
| namespace headless { |
| namespace { |
| |
| int RunContentMain( |
| HeadlessBrowser::Options options, |
| base::OnceCallback<void(HeadlessBrowser*)> on_browser_start_callback) { |
| content::ContentMainParams params(nullptr); |
| #if defined(OS_WIN) |
| // Sandbox info has to be set and initialized. |
| CHECK(options.sandbox_info); |
| params.instance = options.instance; |
| params.sandbox_info = std::move(options.sandbox_info); |
| #elif !defined(OS_ANDROID) |
| params.argc = options.argc; |
| params.argv = options.argv; |
| #endif |
| |
| // TODO(skyostil): Implement custom message pumps. |
| DCHECK(!options.message_pump); |
| |
| std::unique_ptr<HeadlessBrowserImpl> browser(new HeadlessBrowserImpl( |
| std::move(on_browser_start_callback), std::move(options))); |
| HeadlessContentMainDelegate delegate(std::move(browser)); |
| params.delegate = &delegate; |
| return content::ContentMain(params); |
| } |
| |
| } // namespace |
| |
| const base::FilePath::CharType kDefaultProfileName[] = |
| FILE_PATH_LITERAL("Default"); |
| |
| HeadlessBrowserImpl::HeadlessBrowserImpl( |
| base::OnceCallback<void(HeadlessBrowser*)> on_start_callback, |
| HeadlessBrowser::Options options) |
| : on_start_callback_(std::move(on_start_callback)), |
| options_(std::move(options)), |
| browser_main_parts_(nullptr), |
| default_browser_context_(nullptr), |
| agent_host_(nullptr), |
| weak_ptr_factory_(this) {} |
| |
| HeadlessBrowserImpl::~HeadlessBrowserImpl() = default; |
| |
| HeadlessBrowserContext::Builder |
| HeadlessBrowserImpl::CreateBrowserContextBuilder() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return HeadlessBrowserContext::Builder(this); |
| } |
| |
| scoped_refptr<base::SingleThreadTaskRunner> |
| HeadlessBrowserImpl::BrowserMainThread() const { |
| return base::CreateSingleThreadTaskRunnerWithTraits( |
| {content::BrowserThread::UI}); |
| } |
| |
| void HeadlessBrowserImpl::Shutdown() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| browser_contexts_.clear(); |
| if (system_request_context_manager_) { |
| content::BrowserThread::DeleteSoon( |
| content::BrowserThread::IO, FROM_HERE, |
| system_request_context_manager_.release()); |
| } |
| browser_main_parts_->QuitMainMessageLoop(); |
| } |
| |
| std::vector<HeadlessBrowserContext*> |
| HeadlessBrowserImpl::GetAllBrowserContexts() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::vector<HeadlessBrowserContext*> result; |
| result.reserve(browser_contexts_.size()); |
| |
| for (const auto& browser_context_pair : browser_contexts_) { |
| result.push_back(browser_context_pair.second.get()); |
| } |
| |
| return result; |
| } |
| |
| HeadlessBrowserMainParts* HeadlessBrowserImpl::browser_main_parts() const { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return browser_main_parts_; |
| } |
| |
| void HeadlessBrowserImpl::set_browser_main_parts( |
| HeadlessBrowserMainParts* browser_main_parts) { |
| DCHECK(!browser_main_parts_); |
| browser_main_parts_ = browser_main_parts; |
| } |
| |
| void HeadlessBrowserImpl::RunOnStartCallback() { |
| // We don't support the tethering domain on this agent host. |
| agent_host_ = content::DevToolsAgentHost::CreateForBrowser( |
| nullptr, content::DevToolsAgentHost::CreateServerSocketCallback()); |
| |
| PlatformStart(); |
| std::move(on_start_callback_).Run(this); |
| } |
| |
| HeadlessBrowserContext* HeadlessBrowserImpl::CreateBrowserContext( |
| HeadlessBrowserContext::Builder* builder) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| std::unique_ptr<HeadlessBrowserContextImpl> browser_context = |
| HeadlessBrowserContextImpl::Create(builder); |
| |
| if (!browser_context) { |
| return nullptr; |
| } |
| |
| HeadlessBrowserContext* result = browser_context.get(); |
| |
| browser_contexts_[browser_context->Id()] = std::move(browser_context); |
| |
| return result; |
| } |
| |
| void HeadlessBrowserImpl::DestroyBrowserContext( |
| HeadlessBrowserContextImpl* browser_context) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| int erased = browser_contexts_.erase(browser_context->Id()); |
| DCHECK(erased); |
| if (default_browser_context_ == browser_context) |
| SetDefaultBrowserContext(nullptr); |
| } |
| |
| void HeadlessBrowserImpl::SetDefaultBrowserContext( |
| HeadlessBrowserContext* browser_context) { |
| DCHECK(!browser_context || |
| this == HeadlessBrowserContextImpl::From(browser_context)->browser()); |
| |
| default_browser_context_ = browser_context; |
| |
| if (default_browser_context_ && !system_request_context_manager_) { |
| system_request_context_manager_ = |
| HeadlessRequestContextManager::CreateSystemContext( |
| HeadlessBrowserContextImpl::From(browser_context)->options()); |
| } |
| } |
| |
| HeadlessBrowserContext* HeadlessBrowserImpl::GetDefaultBrowserContext() { |
| return default_browser_context_; |
| } |
| |
| base::WeakPtr<HeadlessBrowserImpl> HeadlessBrowserImpl::GetWeakPtr() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| HeadlessWebContents* HeadlessBrowserImpl::GetWebContentsForDevToolsAgentHostId( |
| const std::string& devtools_agent_host_id) { |
| for (HeadlessBrowserContext* context : GetAllBrowserContexts()) { |
| HeadlessWebContents* web_contents = |
| context->GetWebContentsForDevToolsAgentHostId(devtools_agent_host_id); |
| if (web_contents) |
| return web_contents; |
| } |
| return nullptr; |
| } |
| |
| HeadlessWebContentsImpl* HeadlessBrowserImpl::GetWebContentsForWindowId( |
| const int window_id) { |
| for (HeadlessBrowserContext* context : GetAllBrowserContexts()) { |
| for (HeadlessWebContents* web_contents : context->GetAllWebContents()) { |
| auto* contents = HeadlessWebContentsImpl::From(web_contents); |
| if (contents->window_id() == window_id) { |
| return contents; |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| HeadlessBrowserContext* HeadlessBrowserImpl::GetBrowserContextForId( |
| const std::string& id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| auto find_it = browser_contexts_.find(id); |
| if (find_it == browser_contexts_.end()) |
| return nullptr; |
| return find_it->second.get(); |
| } |
| |
| HeadlessDevToolsTarget* HeadlessBrowserImpl::GetDevToolsTarget() { |
| return agent_host_ ? this : nullptr; |
| } |
| |
| std::unique_ptr<HeadlessDevToolsChannel> |
| HeadlessBrowserImpl::CreateDevToolsChannel() { |
| DCHECK(agent_host_); |
| return std::make_unique<HeadlessDevToolsAgentHostClient>(agent_host_); |
| } |
| |
| void HeadlessBrowserImpl::AttachClient(HeadlessDevToolsClient* client) { |
| client->AttachToChannel(CreateDevToolsChannel()); |
| } |
| |
| void HeadlessBrowserImpl::DetachClient(HeadlessDevToolsClient* client) { |
| client->DetachFromChannel(); |
| } |
| |
| bool HeadlessBrowserImpl::IsAttached() { |
| DCHECK(agent_host_); |
| return agent_host_->IsAttached(); |
| } |
| |
| #if defined(OS_WIN) |
| void RunChildProcessIfNeeded(HINSTANCE instance, |
| sandbox::SandboxInterfaceInfo* sandbox_info) { |
| base::CommandLine::Init(0, nullptr); |
| HeadlessBrowser::Options::Builder builder(0, nullptr); |
| builder.SetInstance(instance); |
| builder.SetSandboxInfo(std::move(sandbox_info)); |
| #else |
| void RunChildProcessIfNeeded(int argc, const char** argv) { |
| base::CommandLine::Init(argc, argv); |
| HeadlessBrowser::Options::Builder builder(argc, argv); |
| #endif // defined(OS_WIN) |
| const base::CommandLine& command_line( |
| *base::CommandLine::ForCurrentProcess()); |
| |
| if (!command_line.HasSwitch(::switches::kProcessType)) |
| return; |
| |
| if (command_line.HasSwitch(switches::kUserAgent)) { |
| std::string ua = command_line.GetSwitchValueASCII(switches::kUserAgent); |
| if (net::HttpUtil::IsValidHeaderValue(ua)) |
| builder.SetUserAgent(ua); |
| } |
| |
| exit(RunContentMain(builder.Build(), |
| base::OnceCallback<void(HeadlessBrowser*)>())); |
| } |
| |
| int HeadlessBrowserMain( |
| HeadlessBrowser::Options options, |
| base::OnceCallback<void(HeadlessBrowser*)> on_browser_start_callback) { |
| DCHECK(!on_browser_start_callback.is_null()); |
| #if DCHECK_IS_ON() |
| // The browser can only be initialized once. |
| static bool browser_was_initialized; |
| DCHECK(!browser_was_initialized); |
| browser_was_initialized = true; |
| |
| // Child processes should not end up here. |
| DCHECK(!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| ::switches::kProcessType)); |
| #endif |
| return RunContentMain(std::move(options), |
| std::move(on_browser_start_callback)); |
| } |
| |
| } // namespace headless |