blob: 84228843d64f44720c56d44a75e824f06a823ea7 [file] [log] [blame]
michaeln96f887e22015-04-13 23:58:311// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/after_startup_task_utils.h"
6
7#include "base/lazy_instance.h"
8#include "base/memory/scoped_ptr.h"
9#include "base/metrics/histogram_macros.h"
10#include "base/process/process_info.h"
11#include "base/rand_util.h"
12#include "base/synchronization/cancellation_flag.h"
13#include "base/task_runner.h"
14#include "base/tracked_objects.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/browser/ui/browser_iterator.h"
17#include "chrome/browser/ui/tabs/tab_strip_model.h"
18#include "content/public/browser/browser_thread.h"
19#include "content/public/browser/render_frame_host.h"
20#include "content/public/browser/web_contents.h"
21#include "content/public/browser/web_contents_observer.h"
22
23using content::BrowserThread;
24using content::WebContents;
25using content::WebContentsObserver;
26using StartupCompleteFlag = base::CancellationFlag;
27
28namespace {
29
30struct AfterStartupTask {
31 AfterStartupTask(const tracked_objects::Location& from_here,
32 const scoped_refptr<base::TaskRunner>& task_runner,
33 const base::Closure& task)
34 : from_here(from_here), task_runner(task_runner), task(task) {}
35 ~AfterStartupTask() {}
36
37 const tracked_objects::Location from_here;
38 const scoped_refptr<base::TaskRunner> task_runner;
39 const base::Closure task;
40};
41
42// The flag may be read on any thread, but must only be set on the UI thread.
43base::LazyInstance<StartupCompleteFlag>::Leaky g_startup_complete_flag;
44
45// The queue may only be accessed on the UI thread.
46base::LazyInstance<std::deque<AfterStartupTask*>>::Leaky g_after_startup_tasks;
47
48bool IsBrowserStartupComplete() {
49 // Be sure to initialize the LazyInstance on the main thread since the flag
50 // may only be set on it's initializing thread.
51 if (g_startup_complete_flag == nullptr)
52 return false;
53 return g_startup_complete_flag.Get().IsSet();
54}
55
56void RunTask(scoped_ptr<AfterStartupTask> queued_task) {
57 // We're careful to delete the caller's |task| on the target runner's thread.
58 DCHECK(queued_task->task_runner->RunsTasksOnCurrentThread());
59 queued_task->task.Run();
60}
61
62void ScheduleTask(scoped_ptr<AfterStartupTask> queued_task) {
63 // Spread their execution over a brief time.
64 const int kMinDelaySec = 0;
65 const int kMaxDelaySec = 10;
66 scoped_refptr<base::TaskRunner> target_runner = queued_task->task_runner;
67 tracked_objects::Location from_here = queued_task->from_here;
68 target_runner->PostDelayedTask(
69 from_here, base::Bind(&RunTask, base::Passed(queued_task.Pass())),
70 base::TimeDelta::FromSeconds(base::RandInt(kMinDelaySec, kMaxDelaySec)));
71}
72
73void QueueTask(scoped_ptr<AfterStartupTask> queued_task) {
74 if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
75 BrowserThread::PostTask(
76 BrowserThread::UI, FROM_HERE,
77 base::Bind(QueueTask, base::Passed(queued_task.Pass())));
78 return;
79 }
80
81 // The flag may have been set while the task to invoke this method
82 // on the UI thread was inflight.
83 if (IsBrowserStartupComplete()) {
84 ScheduleTask(queued_task.Pass());
85 return;
86 }
87 g_after_startup_tasks.Get().push_back(queued_task.release());
88}
89
90void SetBrowserStartupIsComplete() {
91 DCHECK_CURRENTLY_ON(BrowserThread::UI);
92#if defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
93 // CurrentProcessInfo::CreationTime() is not available on all platforms.
94 const base::Time process_creation_time =
95 base::CurrentProcessInfo::CreationTime();
96 if (!process_creation_time.is_null()) {
97 UMA_HISTOGRAM_LONG_TIMES("Startup.AfterStartupTaskDelayedUntilTime",
98 base::Time::Now() - process_creation_time);
99 }
100#endif // defined(OS_MACOSX) || defined(OS_WIN) || defined(OS_LINUX)
101 UMA_HISTOGRAM_COUNTS_10000("Startup.AfterStartupTaskCount",
102 g_after_startup_tasks.Get().size());
103 g_startup_complete_flag.Get().Set();
104 for (AfterStartupTask* queued_task : g_after_startup_tasks.Get())
105 ScheduleTask(make_scoped_ptr(queued_task));
106 g_after_startup_tasks.Get().clear();
107
108 // The shrink_to_fit() method is not available for all of our build targets.
109 std::deque<AfterStartupTask*>(g_after_startup_tasks.Get())
110 .swap(g_after_startup_tasks.Get());
111}
112
113// Observes the first visible page load and sets the startup complete
114// flag accordingly.
115class StartupObserver : public WebContentsObserver, public base::NonThreadSafe {
116 public:
117 StartupObserver() : weak_factory_(this) {}
118 ~StartupObserver() override { DCHECK(IsBrowserStartupComplete()); }
119
120 void Start();
121
122 private:
123 void OnStartupComplete() {
124 DCHECK(CalledOnValidThread());
125 SetBrowserStartupIsComplete();
126 delete this;
127 }
128
129 void OnFailsafeTimeout() { OnStartupComplete(); }
130
131 // WebContentsObserver overrides
132 void DidFinishLoad(content::RenderFrameHost* render_frame_host,
133 const GURL& validated_url) override {
134 if (!render_frame_host->GetParent())
135 OnStartupComplete();
136 }
137
138 void DidFailLoad(content::RenderFrameHost* render_frame_host,
139 const GURL& validated_url,
140 int error_code,
gsennton6fbb38692015-06-24 19:23:55141 const base::string16& error_description,
142 bool was_ignored_by_handler) override {
michaeln96f887e22015-04-13 23:58:31143 if (!render_frame_host->GetParent())
144 OnStartupComplete();
145 }
146
147 void WebContentsDestroyed() override { OnStartupComplete(); }
148
149 base::WeakPtrFactory<StartupObserver> weak_factory_;
150
151 DISALLOW_COPY_AND_ASSIGN(StartupObserver);
152};
153
154void StartupObserver::Start() {
155 // Signal completion quickly when there is no first page to load.
156 const int kShortDelaySecs = 3;
157 base::TimeDelta delay = base::TimeDelta::FromSeconds(kShortDelaySecs);
158
159#if !defined(OS_ANDROID)
160 WebContents* contents = nullptr;
161 for (chrome::BrowserIterator iter; !iter.done(); iter.Next()) {
162 contents = (*iter)->tab_strip_model()->GetActiveWebContents();
163 if (contents && contents->GetMainFrame() &&
164 contents->GetMainFrame()->GetVisibilityState() ==
165 blink::WebPageVisibilityStateVisible) {
166 break;
167 }
168 }
169
170 if (contents) {
171 // Give the page time to finish loading.
172 const int kLongerDelayMins = 3;
173 Observe(contents);
174 delay = base::TimeDelta::FromMinutes(kLongerDelayMins);
175 }
176#else
177 // TODO(michaeln): We should probably monitor the initial page load here too,
178 // but since ChromeBrowserMainExtraPartsMetrics doesn't, not doing that yet.
179 const int kAndroidDelaySecs = 10;
180 delay = base::TimeDelta::FromSeconds(kAndroidDelaySecs);
181#endif // !defined(OS_ANDROID)
182
183 BrowserThread::PostDelayedTask(BrowserThread::UI, FROM_HERE,
184 base::Bind(&StartupObserver::OnFailsafeTimeout,
185 weak_factory_.GetWeakPtr()),
186 delay);
187}
188
189} // namespace
190
191void AfterStartupTaskUtils::StartMonitoringStartup() {
192 // The observer is self-deleting.
193 (new StartupObserver)->Start();
194}
195
196void AfterStartupTaskUtils::PostTask(
197 const tracked_objects::Location& from_here,
198 const scoped_refptr<base::TaskRunner>& task_runner,
199 const base::Closure& task) {
200 if (IsBrowserStartupComplete()) {
201 task_runner->PostTask(from_here, task);
202 return;
203 }
204
205 scoped_ptr<AfterStartupTask> queued_task(
206 new AfterStartupTask(from_here, task_runner, task));
207 QueueTask(queued_task.Pass());
208}
209
210void AfterStartupTaskUtils::SetBrowserStartupIsComplete() {
211 ::SetBrowserStartupIsComplete();
212}
213
214bool AfterStartupTaskUtils::IsBrowserStartupComplete() {
215 return ::IsBrowserStartupComplete();
216}
217
218void AfterStartupTaskUtils::UnsafeResetForTesting() {
219 DCHECK(g_after_startup_tasks.Get().empty());
220 if (!IsBrowserStartupComplete())
221 return;
222 g_startup_complete_flag.Get().UnsafeResetForTesting();
223 DCHECK(!IsBrowserStartupComplete());
224}