blob: d20359d0d24638a899f994be4eec5272a64582d2 [file] [log] [blame]
// 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 "chrome/browser/after_startup_task_utils.h"
#include <memory>
#include <utility>
#include "base/containers/circular_deque.h"
#include "base/lazy_instance.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_macros.h"
#include "base/process/process_info.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/synchronization/atomic_flag.h"
#include "base/task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/WebKit/common/page/page_visibility_state.mojom.h"
using content::BrowserThread;
using content::WebContents;
using content::WebContentsObserver;
namespace {
struct AfterStartupTask {
AfterStartupTask(const base::Location& from_here,
const scoped_refptr<base::TaskRunner>& task_runner,
base::OnceClosure task)
: from_here(from_here), task_runner(task_runner), task(std::move(task)) {}
~AfterStartupTask() {}
const base::Location from_here;
const scoped_refptr<base::TaskRunner> task_runner;
base::OnceClosure task;
};
// The flag may be read on any thread, but must only be set on the UI thread.
base::LazyInstance<base::AtomicFlag>::Leaky g_startup_complete_flag;
// The queue may only be accessed on the UI thread.
base::LazyInstance<base::circular_deque<AfterStartupTask*>>::Leaky
g_after_startup_tasks;
bool IsBrowserStartupComplete() {
// Be sure to initialize the LazyInstance on the main thread since the flag
// may only be set on it's initializing thread.
if (g_startup_complete_flag == nullptr)
return false;
return g_startup_complete_flag.Get().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) {
// Spread their execution over a brief time.
const int kMinDelaySec = 0;
const int kMaxDelaySec = 10;
scoped_refptr<base::TaskRunner> target_runner = queued_task->task_runner;
base::Location from_here = queued_task->from_here;
target_runner->PostDelayedTask(
from_here, base::BindOnce(&RunTask, base::Passed(std::move(queued_task))),
base::TimeDelta::FromSeconds(base::RandInt(kMinDelaySec, kMaxDelaySec)));
}
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)) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(QueueTask, base::Passed(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;
}
g_after_startup_tasks.Get().push_back(queued_task.release());
}
void SetBrowserStartupIsComplete() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
// CurrentProcessInfo::CreationTime() is not available on all platforms.
const base::Time process_creation_time =
base::CurrentProcessInfo::CreationTime();
if (!process_creation_time.is_null()) {
UMA_HISTOGRAM_LONG_TIMES("Startup.AfterStartupTaskDelayedUntilTime",
base::Time::Now() - process_creation_time);
}
#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
UMA_HISTOGRAM_COUNTS_10000("Startup.AfterStartupTaskCount",
g_after_startup_tasks.Get().size());
g_startup_complete_flag.Get().Set();
for (AfterStartupTask* queued_task : g_after_startup_tasks.Get())
ScheduleTask(base::WrapUnique(queued_task));
g_after_startup_tasks.Get().clear();
// The shrink_to_fit() method is not available for all of our build targets.
base::circular_deque<AfterStartupTask*>(g_after_startup_tasks.Get())
.swap(g_after_startup_tasks.Get());
}
// Observes the first visible page load and sets the startup complete
// flag accordingly.
class StartupObserver : public WebContentsObserver {
public:
StartupObserver() : weak_factory_(this) {}
~StartupObserver() override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(IsBrowserStartupComplete());
}
void Start();
private:
void OnStartupComplete() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
SetBrowserStartupIsComplete();
delete this;
}
void OnFailsafeTimeout() { OnStartupComplete(); }
// WebContentsObserver overrides
void DidFinishLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url) override {
if (!render_frame_host->GetParent())
OnStartupComplete();
}
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override {
if (!render_frame_host->GetParent())
OnStartupComplete();
}
void WebContentsDestroyed() override { OnStartupComplete(); }
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<StartupObserver> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(StartupObserver);
};
void StartupObserver::Start() {
// Signal completion quickly when there is no first page to load.
const int kShortDelaySecs = 3;
base::TimeDelta delay = base::TimeDelta::FromSeconds(kShortDelaySecs);
#if !defined(OS_ANDROID)
WebContents* contents = nullptr;
for (auto* browser : *BrowserList::GetInstance()) {
contents = browser->tab_strip_model()->GetActiveWebContents();
if (contents && contents->GetMainFrame() &&
contents->GetMainFrame()->GetVisibilityState() ==
blink::mojom::PageVisibilityState::kVisible) {
break;
}
}
if (contents) {
// Give the page time to finish loading.
const int kLongerDelayMins = 3;
Observe(contents);
delay = base::TimeDelta::FromMinutes(kLongerDelayMins);
}
#else
// Startup completion is signaled via AfterStartupTaskUtils.java,
// this is just a failsafe timeout.
const int kLongerDelayMins = 3;
delay = base::TimeDelta::FromMinutes(kLongerDelayMins);
#endif // !defined(OS_ANDROID)
BrowserThread::PostDelayedTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&StartupObserver::OnFailsafeTimeout,
weak_factory_.GetWeakPtr()),
delay);
}
} // namespace
AfterStartupTaskUtils::Runner::Runner(
scoped_refptr<base::TaskRunner> destination_runner)
: destination_runner_(std::move(destination_runner)) {
DCHECK(destination_runner_);
}
AfterStartupTaskUtils::Runner::~Runner() = default;
bool AfterStartupTaskUtils::Runner::PostDelayedTask(
const base::Location& from_here,
base::OnceClosure task,
base::TimeDelta delay) {
DCHECK(delay.is_zero());
AfterStartupTaskUtils::PostTask(from_here, destination_runner_,
std::move(task));
return true;
}
bool AfterStartupTaskUtils::Runner::RunsTasksInCurrentSequence() const {
return destination_runner_->RunsTasksInCurrentSequence();
}
void AfterStartupTaskUtils::StartMonitoringStartup() {
// The observer is self-deleting.
(new StartupObserver)->Start();
}
void AfterStartupTaskUtils::PostTask(
const base::Location& from_here,
const scoped_refptr<base::TaskRunner>& 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(g_after_startup_tasks.Get().empty());
if (!IsBrowserStartupComplete())
return;
g_startup_complete_flag.Get().UnsafeResetForTesting();
DCHECK(!IsBrowserStartupComplete());
}