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