blob: 560bbe86745f79802cd8a2f8e65809dbfa57eaeb [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/after_startup_task_utils.h"
#include "base/containers/circular_deque.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/no_destructor.h"
#include "base/process/process.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task/sequenced_task_runner.h"
#include "base/trace_event/trace_event.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser_finder.h"
#include "components/performance_manager/public/graph/graph.h"
#include "components/performance_manager/public/graph/page_node.h"
#include "components/performance_manager/public/performance_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ui/ash/login/login_display_host.h"
#endif
using content::BrowserThread;
namespace {
using performance_manager::PerformanceManager;
struct AfterStartupTask {
AfterStartupTask(const base::Location& from_here,
const scoped_refptr<base::SequencedTaskRunner>& task_runner,
base::OnceClosure task)
: from_here(from_here), task_runner(task_runner), task(std::move(task)) {}
~AfterStartupTask() = default;
const base::Location from_here;
const scoped_refptr<base::SequencedTaskRunner> task_runner;
base::OnceClosure task;
};
// The flag may be read on any thread, but must only be set on the UI thread.
base::AtomicFlag& GetStartupCompleteFlag() {
static base::NoDestructor<base::AtomicFlag> startup_complete_flag;
return *startup_complete_flag;
}
// The queue may only be accessed on the UI thread.
base::circular_deque<AfterStartupTask*>& GetAfterStartupTasks() {
static base::NoDestructor<base::circular_deque<AfterStartupTask*>>
after_startup_tasks;
return *after_startup_tasks;
}
bool IsBrowserStartupComplete() {
return GetStartupCompleteFlag().IsSet();
}
void RunTask(std::unique_ptr<AfterStartupTask> queued_task) {
// We're careful to delete the caller's |task| on the target runner's thread.
DCHECK(queued_task->task_runner->RunsTasksInCurrentSequence());
std::move(queued_task->task).Run();
}
void ScheduleTask(std::unique_ptr<AfterStartupTask> queued_task) {
scoped_refptr<base::SequencedTaskRunner> target_runner =
queued_task->task_runner;
base::Location from_here = queued_task->from_here;
target_runner->PostTask(from_here,
base::BindOnce(&RunTask, std::move(queued_task)));
}
void QueueTask(std::unique_ptr<AfterStartupTask> queued_task) {
DCHECK(queued_task);
// Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167
// for details.
CHECK(queued_task->task);
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
// Posted with USER_VISIBLE priority to avoid this becoming an after startup
// task itself.
content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE})
->PostTask(FROM_HERE,
base::BindOnce(QueueTask, std::move(queued_task)));
return;
}
// The flag may have been set while the task to invoke this method
// on the UI thread was inflight.
if (IsBrowserStartupComplete()) {
ScheduleTask(std::move(queued_task));
return;
}
GetAfterStartupTasks().push_back(queued_task.release());
}
void SetBrowserStartupIsComplete() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (IsBrowserStartupComplete())
return;
size_t browser_count = 0;
#if !BUILDFLAG(IS_ANDROID)
browser_count = chrome::GetTotalBrowserCount();
#endif // !BUILDFLAG(IS_ANDROID)
TRACE_EVENT_INSTANT1("startup", "Startup.StartupComplete",
TRACE_EVENT_SCOPE_GLOBAL, "BrowserCount", browser_count);
GetStartupCompleteFlag().Set();
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) || \
BUILDFLAG(IS_CHROMEOS)
// Process::Current().CreationTime() is not available on all platforms.
const base::Time process_creation_time =
base::Process::Current().CreationTime();
if (!process_creation_time.is_null()) {
UMA_HISTOGRAM_LONG_TIMES("Startup.AfterStartupTaskDelayedUntilTime",
base::Time::Now() - process_creation_time);
}
#endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX) ||
// BUILDFLAG(IS_CHROMEOS)
UMA_HISTOGRAM_COUNTS_10000("Startup.AfterStartupTaskCount",
GetAfterStartupTasks().size());
for (AfterStartupTask* queued_task : GetAfterStartupTasks()) {
ScheduleTask(base::WrapUnique(queued_task));
}
GetAfterStartupTasks().clear();
GetAfterStartupTasks().shrink_to_fit();
}
// Observes the first visible page load and sets the startup complete
// flag accordingly. Ownership is passed to the Performance Manager
// after creation.
class StartupObserver : public performance_manager::GraphOwned,
public performance_manager::PageNodeObserver {
public:
StartupObserver(const StartupObserver&) = delete;
StartupObserver& operator=(const StartupObserver&) = delete;
~StartupObserver() override = default;
static void Start();
private:
using LoadingState = performance_manager::PageNode::LoadingState;
StartupObserver() = default;
void OnStartupComplete() {
CHECK(PerformanceManager::IsAvailable());
SetBrowserStartupIsComplete();
// This will result in delete getting called.
TakeFromGraph();
}
// GraphOwned overrides
void OnPassedToGraph(performance_manager::Graph* graph) override {
graph->AddPageNodeObserver(this);
}
void OnTakenFromGraph(performance_manager::Graph* graph) override {
graph->RemovePageNodeObserver(this);
}
// PageNodeObserver overrides
void OnLoadingStateChanged(const performance_manager::PageNode* page_node,
LoadingState previous_state) override {
// Only interested in visible PageNodes
if (page_node->IsVisible()) {
if (page_node->GetLoadingState() == LoadingState::kLoadedIdle ||
page_node->GetLoadingState() == LoadingState::kLoadingTimedOut) {
OnStartupComplete();
}
}
}
void TakeFromGraph() {
// Remove this object from the performance manager. This will
// cause the object to be deleted.
CHECK(PerformanceManager::IsAvailable());
PerformanceManager::GetGraph()->TakeFromGraph(this);
}
};
// static
void StartupObserver::Start() {
CHECK(PerformanceManager::IsAvailable());
// Pass a new StartupObserver to the performance manager so we can get
// notified when loading completes. The performance manager takes ownership.
PerformanceManager::GetGraph()->PassToGraph(
base::WrapUnique(new StartupObserver()));
}
} // namespace
void AfterStartupTaskUtils::StartMonitoringStartup() {
#if BUILDFLAG(IS_CHROMEOS)
// If we are on a login screen which does not expect WebUI to be loaded,
// Browser won't be created at startup.
if (ash::LoginDisplayHost::default_host() &&
!ash::LoginDisplayHost::default_host()->IsWebUIStarted()) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&SetBrowserStartupIsComplete));
return;
}
#endif
// For Android, startup completion is signaled via
// AfterStartupTaskUtils.java. We do not use the StartupObserver.
#if !BUILDFLAG(IS_ANDROID)
StartupObserver::Start();
#endif // !BUILDFLAG(IS_ANDROID)
// Add failsafe timeout
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE, base::BindOnce(&SetBrowserStartupIsComplete),
base::Minutes(3));
}
void AfterStartupTaskUtils::PostTask(
const base::Location& from_here,
const scoped_refptr<base::SequencedTaskRunner>& destination_runner,
base::OnceClosure task) {
if (IsBrowserStartupComplete()) {
destination_runner->PostTask(from_here, std::move(task));
return;
}
std::unique_ptr<AfterStartupTask> queued_task(
new AfterStartupTask(from_here, destination_runner, std::move(task)));
QueueTask(std::move(queued_task));
}
void AfterStartupTaskUtils::SetBrowserStartupIsCompleteForTesting() {
::SetBrowserStartupIsComplete();
}
void AfterStartupTaskUtils::SetBrowserStartupIsComplete() {
::SetBrowserStartupIsComplete();
}
bool AfterStartupTaskUtils::IsBrowserStartupComplete() {
return ::IsBrowserStartupComplete();
}
void AfterStartupTaskUtils::UnsafeResetForTesting() {
DCHECK(GetAfterStartupTasks().empty());
if (!IsBrowserStartupComplete())
return;
GetStartupCompleteFlag().UnsafeResetForTesting(); // IN-TEST
DCHECK(!IsBrowserStartupComplete());
}