Chromium Code Reviews| Index: chrome/browser/after_startup_task.cc |
| diff --git a/chrome/browser/after_startup_task.cc b/chrome/browser/after_startup_task.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..c9ff4ef5468e2040dbef7237030510521300cbce |
| --- /dev/null |
| +++ b/chrome/browser/after_startup_task.cc |
| @@ -0,0 +1,198 @@ |
| +// Copyright (c) 2015 The Chromium Authors. All rights reserved. |
|
cmumford
2015/03/27 21:59:58
Nit: "(c)"
michaeln
2015/04/03 19:51:37
Done.
|
| +// 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.h" |
| + |
| +#include "base/lazy_instance.h" |
| +#include "base/memory/scoped_ptr.h" |
| +#include "base/metrics/histogram_macros.h" |
| +#include "base/process/process_info.h" |
| +#include "base/rand_util.h" |
| +#include "base/synchronization/cancellation_flag.h" |
| +#include "base/task_runner.h" |
| +#include "base/tracked_objects.h" |
| +#include "chrome/browser/ui/browser.h" |
| +#include "chrome/browser/ui/browser_iterator.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" |
| + |
| +using content::BrowserThread; |
| +using content::WebContents; |
| +using content::WebContentsObserver; |
| +using StartupCompleteFlag = base::CancellationFlag; |
| + |
| +namespace { |
| + |
| +struct AfterStartupTask { |
| + AfterStartupTask( |
| + const tracked_objects::Location& from_here, |
| + const scoped_refptr<base::TaskRunner>& task_runner, |
| + const base::Closure& task) |
| + : from_here(from_here), task_runner(task_runner), task(task) {} |
| + ~AfterStartupTask() {} |
| + |
| + const tracked_objects::Location from_here; |
| + const scoped_refptr<base::TaskRunner> task_runner; |
| + const base::Closure task; |
| +}; |
| + |
| +class StartupObserver; |
| +void SetBrowserStartupIsComplete(); |
| +bool IsBrowserStartupComplete(); |
| +void QueueTask(scoped_ptr<AfterStartupTask> queued_task); |
| +void ScheduleTask(scoped_ptr<AfterStartupTask> queued_task); |
| +void RunTask(scoped_ptr<AfterStartupTask> queued_task); |
| + |
| +base::LazyInstance<StartupCompleteFlag>::Leaky g_startup_complete_flag; |
| +base::LazyInstance<std::deque<AfterStartupTask*>>::Leaky g_after_startup_tasks; |
| + |
| +void SetBrowserStartupIsComplete() { |
| + DCHECK(BrowserThread::CurrentlyOn(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( |
| + "AfterStartupTasks.RunTime", base::Time::Now() - process_creation_time); |
| + } |
| +#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) |
| + UMA_HISTOGRAM_COUNTS_100( |
| + "AfterStartupTasks.Count", g_after_startup_tasks.Get().size()); |
| + g_startup_complete_flag.Get().Set(); |
| + for (AfterStartupTask* queued_task : g_after_startup_tasks.Get()) |
| + ScheduleTask(make_scoped_ptr(queued_task)); |
| + g_after_startup_tasks.Get().clear(); |
| + g_after_startup_tasks.Get().shrink_to_fit(); |
|
michaeln
2015/03/27 21:35:42
bummer, shrink_to_fit() isn't implemented everywhe
|
| +} |
| + |
| +bool IsBrowserStartupComplete() { |
| + // Be sure to allocate the flag on the main thread. |
| + if (g_startup_complete_flag == nullptr) |
| + return false; |
| + return g_startup_complete_flag.Get().IsSet(); |
| +} |
| + |
| +void QueueTask(scoped_ptr<AfterStartupTask> queued_task) { |
| + if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { |
| + BrowserThread::PostTask( |
| + BrowserThread::UI, |
| + FROM_HERE, |
| + base::Bind(QueueTask, base::Passed(queued_task.Pass()))); |
| + return; |
| + } |
| + if (IsBrowserStartupComplete()) { |
| + ScheduleTask(queued_task.Pass()); |
| + return; |
| + } |
| + g_after_startup_tasks.Get().push_back(queued_task.release()); |
| +} |
| + |
| +void ScheduleTask(scoped_ptr<AfterStartupTask> queued_task) { |
| + // Spread their execution over a brief time. |
| + const int kMinDelay = 0; |
| + const int kMaxDelay = 10; |
| + scoped_refptr<base::TaskRunner> target_runner = queued_task->task_runner; |
| + tracked_objects::Location from_here = queued_task->from_here; |
| + target_runner->PostDelayedTask( |
| + from_here, |
| + base::Bind(&RunTask, base::Passed(queued_task.Pass())), |
| + base::TimeDelta::FromSeconds(base::RandInt(kMinDelay, kMaxDelay))); |
| +} |
| + |
| +void RunTask(scoped_ptr<AfterStartupTask> queued_task) { |
| + // We're careful to delete the caller's |task| on the target runner's thread. |
| + DCHECK(queued_task->task_runner->RunsTasksOnCurrentThread()); |
| + queued_task->task.Run(); |
| +} |
| + |
| +// Observes the first page load and set the startup complete flag accordingly. |
| +class StartupObserver : public WebContentsObserver { |
| + public: |
| + StartupObserver() : weak_factory_(this) {} |
| + ~StartupObserver() override { DCHECK(IsBrowserStartupComplete()); } |
|
cmumford
2015/03/27 21:59:58
Aren't DCHECKs for programmer errors? If the fails
michaeln
2015/03/27 22:08:00
I guess i'm trying to document that this class isn
|
| + |
| + void Start(); |
| + |
| + private: |
| + void OnStartupComplete() { |
| + 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(); } |
| + |
| + 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) |
|
cmumford
2015/03/27 21:59:58
Why not Android?
michaeln
2015/03/27 22:07:59
https://codereview.chromium.org/949293002/#msg35
F
|
| + WebContents* contents = nullptr; |
| + for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) { |
| + contents = (*iter)->tab_strip_model()->GetActiveWebContents(); |
| + if (contents) |
| + break; |
| + } |
| + |
| + if (contents) { |
| + // Give the page time to finish loading. |
| + const int kLongerDelayMins = 3; |
| + Observe(contents); |
| + delay = base::TimeDelta::FromMinutes(kLongerDelayMins); |
| + } |
| +#endif // !defined(OS_ANDROID) |
| + |
| + BrowserThread::PostDelayedTask( |
| + BrowserThread::UI, FROM_HERE, |
| + base::Bind(&StartupObserver::OnFailsafeTimeout, |
| + weak_factory_.GetWeakPtr()), |
| + delay); |
| +} |
| + |
| +} // namespace |
| + |
| +void StartMonitoringStartup() { |
| + // The observer is self-deleting. |
| + (new StartupObserver)->Start(); |
| +} |
| + |
| +void PostAfterStartupTask( |
| + const tracked_objects::Location& from_here, |
| + const scoped_refptr<base::TaskRunner>& task_runner, |
| + const base::Closure& task) { |
| + if (IsBrowserStartupComplete()) { |
| + task_runner->PostTask(from_here, task); |
| + return; |
| + } |
| + |
| + scoped_ptr<AfterStartupTask> queued_task( |
| + new AfterStartupTask(from_here, task_runner, task)); |
| + QueueTask(queued_task.Pass()); |
| +} |