// 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 #include "base/lazy_instance.h" #include "base/macros.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 "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" 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& 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 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::Leaky g_startup_complete_flag; // The queue may only be accessed on the UI thread. base::LazyInstance>::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(scoped_ptr 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 queued_task) { // Spread their execution over a brief time. const int kMinDelaySec = 0; const int kMaxDelaySec = 10; scoped_refptr 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(std::move(queued_task))), base::TimeDelta::FromSeconds(base::RandInt(kMinDelaySec, kMaxDelaySec))); } void QueueTask(scoped_ptr queued_task) { if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { BrowserThread::PostTask( BrowserThread::UI, FROM_HERE, base::Bind(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(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(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, bool was_ignored_by_handler) override { if (!render_frame_host->GetParent()) OnStartupComplete(); } void WebContentsDestroyed() override { OnStartupComplete(); } base::WeakPtrFactory 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::WebPageVisibilityStateVisible) { 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::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& task_runner, const base::Closure& task) { if (IsBrowserStartupComplete()) { task_runner->PostTask(from_here, task); return; } scoped_ptr queued_task( new AfterStartupTask(from_here, task_runner, 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()); }