| // 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 <memory> | 
 | #include <utility> | 
 |  | 
 | #include "base/containers/circular_deque.h" | 
 | #include "base/lazy_instance.h" | 
 | #include "base/macros.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/process/process.h" | 
 | #include "base/rand_util.h" | 
 | #include "base/sequence_checker.h" | 
 | #include "base/sequenced_task_runner.h" | 
 | #include "base/synchronization/atomic_flag.h" | 
 | #include "build/build_config.h" | 
 | #include "build/chromeos_buildflags.h" | 
 | #include "content/public/browser/browser_task_traits.h" | 
 | #include "content/public/browser/browser_thread.h" | 
 | #include "content/public/browser/navigation_handle.h" | 
 | #include "content/public/browser/render_frame_host.h" | 
 | #include "content/public/browser/web_contents.h" | 
 | #include "content/public/browser/web_contents_observer.h" | 
 | #include "content/public/common/page_visibility_state.h" | 
 |  | 
 | #if !defined(OS_ANDROID) | 
 | #include "chrome/browser/ui/browser.h" | 
 | #include "chrome/browser/ui/browser_list.h" | 
 | #include "chrome/browser/ui/tabs/tab_strip_model.h" | 
 | #endif | 
 |  | 
 | // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch | 
 | // of lacros-chrome is complete. | 
 | #if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) | 
 | #include "ui/views/linux_ui/linux_ui.h" | 
 | #endif | 
 |  | 
 | using content::BrowserThread; | 
 | using content::WebContents; | 
 | using content::WebContentsObserver; | 
 |  | 
 | namespace { | 
 |  | 
 | struct AfterStartupTask { | 
 |   AfterStartupTask(const base::Location& from_here, | 
 |                    const scoped_refptr<base::SequencedTaskRunner>& task_runner, | 
 |                    base::OnceClosure task) | 
 |       : from_here(from_here), task_runner(task_runner), task(std::move(task)) {} | 
 |   ~AfterStartupTask() {} | 
 |  | 
 |   const base::Location from_here; | 
 |   const scoped_refptr<base::SequencedTaskRunner> task_runner; | 
 |   base::OnceClosure task; | 
 | }; | 
 |  | 
 | // The flag may be read on any thread, but must only be set on the UI thread. | 
 | base::LazyInstance<base::AtomicFlag>::Leaky g_startup_complete_flag; | 
 |  | 
 | // The queue may only be accessed on the UI thread. | 
 | base::LazyInstance<base::circular_deque<AfterStartupTask*>>::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.IsCreated()) | 
 |     return false; | 
 |   return g_startup_complete_flag.Get().IsSet(); | 
 | } | 
 |  | 
 | void RunTask(std::unique_ptr<AfterStartupTask> queued_task) { | 
 |   // We're careful to delete the caller's |task| on the target runner's thread. | 
 |   DCHECK(queued_task->task_runner->RunsTasksInCurrentSequence()); | 
 |   std::move(queued_task->task).Run(); | 
 | } | 
 |  | 
 | void ScheduleTask(std::unique_ptr<AfterStartupTask> queued_task) { | 
 |   scoped_refptr<base::SequencedTaskRunner> target_runner = | 
 |       queued_task->task_runner; | 
 |   base::Location from_here = queued_task->from_here; | 
 |   target_runner->PostTask(from_here, | 
 |                           base::BindOnce(&RunTask, std::move(queued_task))); | 
 | } | 
 |  | 
 | void QueueTask(std::unique_ptr<AfterStartupTask> queued_task) { | 
 |   DCHECK(queued_task); | 
 |  | 
 |   // Use CHECK instead of DCHECK to crash earlier. See http://crbug.com/711167 | 
 |   // for details. | 
 |   CHECK(queued_task->task); | 
 |  | 
 |   if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) { | 
 |     // Posted with USER_VISIBLE priority to avoid this becoming an after startup | 
 |     // task itself. | 
 |     content::GetUIThreadTaskRunner({base::TaskPriority::USER_VISIBLE}) | 
 |         ->PostTask(FROM_HERE, | 
 |                    base::BindOnce(QueueTask, 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_MAC) || defined(OS_WIN) || defined(OS_LINUX) || \ | 
 |     defined(OS_CHROMEOS) | 
 |   // Process::Current().CreationTime() is not available on all platforms. | 
 |   const base::Time process_creation_time = | 
 |       base::Process::Current().CreationTime(); | 
 |   if (!process_creation_time.is_null()) { | 
 |     UMA_HISTOGRAM_LONG_TIMES("Startup.AfterStartupTaskDelayedUntilTime", | 
 |                              base::Time::Now() - process_creation_time); | 
 |   } | 
 | #endif  // defined(OS_MAC) || defined(OS_WIN) || defined(OS_LINUX) || | 
 |         // defined(OS_CHROMEOS) | 
 |   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(base::WrapUnique(queued_task)); | 
 |   g_after_startup_tasks.Get().clear(); | 
 |   g_after_startup_tasks.Get().shrink_to_fit(); | 
 |  | 
 | // TODO(crbug.com/1052397): Revisit the macro expression once build flag switch | 
 | // of lacros-chrome is complete. | 
 | #if defined(OS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS) | 
 |   // Make sure we complete the startup notification sequence, or launchers will | 
 |   // get confused by not receiving the expected message from the main process. | 
 |   views::LinuxUI* linux_ui = views::LinuxUI::instance(); | 
 |   if (linux_ui) | 
 |     linux_ui->NotifyWindowManagerStartupComplete(); | 
 | #endif | 
 | } | 
 |  | 
 | // Observes the first visible page load and sets the startup complete | 
 | // flag accordingly. | 
 | class StartupObserver : public WebContentsObserver { | 
 |  public: | 
 |   StartupObserver() {} | 
 |   ~StartupObserver() override { | 
 |     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |     DCHECK(IsBrowserStartupComplete()); | 
 |   } | 
 |  | 
 |   void Start(); | 
 |  | 
 |  private: | 
 |   void OnStartupComplete() { | 
 |     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |     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) override { | 
 |     if (!render_frame_host->GetParent()) | 
 |       OnStartupComplete(); | 
 |   } | 
 |  | 
 |   // Starting the browser with a file download url will not result in | 
 |   // DidFinishLoad firing, so watch for this case too. crbug.com/1006954 | 
 |   void DidFinishNavigation( | 
 |       content::NavigationHandle* navigation_handle) override { | 
 |     if (navigation_handle->IsInMainFrame() && navigation_handle->IsDownload()) | 
 |       OnStartupComplete(); | 
 |   } | 
 |  | 
 |   void WebContentsDestroyed() override { OnStartupComplete(); } | 
 |  | 
 |   SEQUENCE_CHECKER(sequence_checker_); | 
 |  | 
 |   base::WeakPtrFactory<StartupObserver> weak_factory_{this}; | 
 |  | 
 |   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() == | 
 |             content::PageVisibilityState::kVisible) { | 
 |       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) | 
 |  | 
 |   content::GetUIThreadTaskRunner({})->PostDelayedTask( | 
 |       FROM_HERE, | 
 |       base::BindOnce(&StartupObserver::OnFailsafeTimeout, | 
 |                      weak_factory_.GetWeakPtr()), | 
 |       delay); | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | void AfterStartupTaskUtils::StartMonitoringStartup() { | 
 |   // The observer is self-deleting. | 
 |   (new StartupObserver)->Start(); | 
 | } | 
 |  | 
 | void AfterStartupTaskUtils::PostTask( | 
 |     const base::Location& from_here, | 
 |     const scoped_refptr<base::SequencedTaskRunner>& destination_runner, | 
 |     base::OnceClosure task) { | 
 |   if (IsBrowserStartupComplete()) { | 
 |     destination_runner->PostTask(from_here, std::move(task)); | 
 |     return; | 
 |   } | 
 |  | 
 |   std::unique_ptr<AfterStartupTask> queued_task( | 
 |       new AfterStartupTask(from_here, destination_runner, std::move(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()); | 
 | } |