Chromium Code Reviews| Index: chrome/browser/after_startup_task_utils.cc |
| diff --git a/chrome/browser/after_startup_task_utils.cc b/chrome/browser/after_startup_task_utils.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..2f901966170cd0f342c7f109eb09d029e8c32320 |
| --- /dev/null |
| +++ b/chrome/browser/after_startup_task_utils.cc |
| @@ -0,0 +1,217 @@ |
| +// 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 "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; |
|
Nico
2015/04/09 20:10:15
This is used in a single place. Why is this useful
michaeln
2015/04/09 21:48:39
This should help tip readers off to the fact that
|
| + |
| +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; |
| +}; |
| + |
| +// The flag may be read on any thread, but must only be set on the UI thread. |
| +base::LazyInstance<StartupCompleteFlag>::Leaky g_startup_complete_flag; |
| + |
| +// The queue may only be accessed on the UI thread. |
| +base::LazyInstance<std::deque<AfterStartupTask*>>::Leaky g_after_startup_tasks; |
| + |
| +bool IsBrowserStartupComplete() { |
| + // Be sure to allocate the flag on the main thread. |
|
Nico
2015/04/09 20:10:15
(i found this comment confusing. i get it now, but
michaeln
2015/04/09 21:48:39
Done
how about 'initialize the lazyinstance on th
|
| + if (g_startup_complete_flag == nullptr) |
| + return false; |
| + return g_startup_complete_flag.Get().IsSet(); |
| +} |
| + |
| +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(); |
| +} |
| + |
| +void ScheduleTask(scoped_ptr<AfterStartupTask> queued_task) { |
| + // Spread their execution over a brief time. |
| + const int kMinDelay = 0; |
| + const int kMaxDelay = 10; |
|
Nico
2015/04/09 20:10:15
suffix name with unit (kMinDelaySec, kMaxDelaySec)
michaeln
2015/04/09 21:48:39
Done.
|
| + 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 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; |
| + } |
| + |
| + // The flag may have been set while the task to invoke this method |
| + // on the UI thread was inflight. |
| + if (IsBrowserStartupComplete()) { |
| + ScheduleTask(queued_task.Pass()); |
| + 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("AfterStartupTasks.DelayedUntilTime", |
| + base::Time::Now() - process_creation_time); |
| + } |
| +#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX) |
| + UMA_HISTOGRAM_COUNTS_10000("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(); |
| + |
| + // The shrink_to_fit() method is not available for all of our build targets. |
| + std::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 base::NonThreadSafe { |
| + public: |
| + StartupObserver() : weak_factory_(this) {} |
| + ~StartupObserver() override { DCHECK(IsBrowserStartupComplete()); } |
| + |
| + void Start(); |
| + |
| + private: |
| + void OnStartupComplete() { |
| + DCHECK(CalledOnValidThread()); |
| + 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) |
| + WebContents* contents = nullptr; |
| + for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) { |
| + contents = (*iter)->tab_strip_model()->GetActiveWebContents(); |
| + if (contents && contents->GetMainFrame() && |
| + contents->GetMainFrame()->GetVisibilityState() == |
| + blink::WebPageVisibilityStateVisible) { |
| + 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) |
|
Nico
2015/04/09 20:10:15
shouldn't we default to more than 3 unconditional
michaeln
2015/04/09 21:48:39
i think android should probably monitor the initia
|
| + |
| + BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE, |
| + base::Bind(&StartupObserver::OnFailsafeTimeout, |
| + weak_factory_.GetWeakPtr()), |
| + delay); |
| +} |
| + |
| +} // namespace |
| + |
| +void AfterStartupTaskUtils::StartMonitoringStartup() { |
| + // The observer is self-deleting. |
| + (new StartupObserver)->Start(); |
| +} |
| + |
| +void AfterStartupTaskUtils::PostTask( |
| + 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()); |
| +} |
| + |
| +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()); |
| +} |