123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203 |
- // Copyright 2020 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 "components/performance_manager/public/decorators/page_load_tracker_decorator_helper.h"
- #include "base/bind.h"
- #include "base/memory/raw_ptr.h"
- #include "components/performance_manager/decorators/page_load_tracker_decorator.h"
- #include "components/performance_manager/graph/page_node_impl.h"
- #include "components/performance_manager/performance_manager_impl.h"
- #include "components/performance_manager/public/performance_manager.h"
- #include "content/public/browser/browser_thread.h"
- #include "content/public/browser/web_contents.h"
- #include "content/public/browser/web_contents_observer.h"
- namespace performance_manager {
- namespace {
- void NotifyPageLoadTrackerDecoratorOnPMSequence(content::WebContents* contents,
- void (*method)(PageNodeImpl*)) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- PerformanceManagerImpl::CallOnGraphImpl(
- FROM_HERE,
- base::BindOnce(
- [](base::WeakPtr<PageNode> node, void (*method)(PageNodeImpl*)) {
- if (node) {
- PageNodeImpl* page_node = PageNodeImpl::FromNode(node.get());
- method(page_node);
- }
- },
- PerformanceManager::GetPrimaryPageNodeForWebContents(contents),
- method));
- }
- } // namespace
- // Listens to content::WebContentsObserver notifications for a given WebContents
- // and updates the PageLoadTracker accordingly. Destroys itself when the
- // WebContents it observes is destroyed.
- class PageLoadTrackerDecoratorHelper::WebContentsObserver
- : public content::WebContentsObserver {
- public:
- explicit WebContentsObserver(content::WebContents* web_contents,
- PageLoadTrackerDecoratorHelper* outer)
- : content::WebContentsObserver(web_contents),
- outer_(outer),
- prev_(nullptr),
- next_(outer->first_web_contents_observer_) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK(web_contents);
- if (next_) {
- DCHECK(!next_->prev_);
- next_->prev_ = this;
- }
- outer_->first_web_contents_observer_ = this;
- // |web_contents| must not be loading when it starts being tracked by this
- // observer. Otherwise, loading state wouldn't be tracked correctly.
- DCHECK(!web_contents->ShouldShowLoadingUI());
- }
- WebContentsObserver(const WebContentsObserver&) = delete;
- WebContentsObserver& operator=(const WebContentsObserver&) = delete;
- ~WebContentsObserver() override {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- }
- // content::WebContentsObserver:
- void DidStartLoading() override {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- DCHECK(web_contents()->IsLoading());
- DCHECK_EQ(loading_state_, LoadingState::kNotLoading);
- // Only observe top-level navigation to a different document.
- if (!web_contents()->ShouldShowLoadingUI())
- return;
- loading_state_ = LoadingState::kWaitingForNavigation;
- NotifyPageLoadTrackerDecoratorOnPMSequence(
- web_contents(), &PageLoadTrackerDecorator::DidStartLoading);
- }
- void PrimaryPageChanged(content::Page& page) override {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // Only observe top-level navigation that will show navigation UI.
- if (!web_contents()->ShouldShowLoadingUI())
- return;
- DCHECK(web_contents()->IsLoading());
- // Return if the state is already updated, since it can be called in
- // multiple times between DidStartLoading() and DidStopLoading().
- if (loading_state_ == LoadingState::kLoading)
- return;
- DCHECK_EQ(loading_state_, LoadingState::kWaitingForNavigation);
- loading_state_ = LoadingState::kLoading;
- NotifyPageLoadTrackerDecoratorOnPMSequence(
- web_contents(), &PageLoadTrackerDecorator::PrimaryPageChanged);
- }
- void DidStopLoading() override {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- // The state can be |kNotLoading| if this isn't a top-level navigation to a
- // different document.
- if (loading_state_ == LoadingState::kNotLoading)
- return;
- loading_state_ = LoadingState::kNotLoading;
- NotifyPageLoadTrackerDecoratorOnPMSequence(
- web_contents(), &PageLoadTrackerDecorator::DidStopLoading);
- }
- void WebContentsDestroyed() override {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DetachAndDestroy();
- }
- // Removes the WebContentsObserver from the linked list and deletes it.
- void DetachAndDestroy() {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
- if (prev_) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(prev_->sequence_checker_);
- DCHECK_EQ(prev_->next_, this);
- prev_->next_ = next_;
- } else {
- DCHECK_EQ(outer_->first_web_contents_observer_, this);
- outer_->first_web_contents_observer_ = next_;
- }
- if (next_) {
- DCHECK_CALLED_ON_VALID_SEQUENCE(next_->sequence_checker_);
- DCHECK_EQ(next_->prev_, this);
- next_->prev_ = prev_;
- }
- delete this;
- }
- private:
- // TODO(https://crbug.com/1048719): Extract the logic to manage a linked list
- // of WebContentsObservers to a helper class.
- const raw_ptr<PageLoadTrackerDecoratorHelper> outer_;
- raw_ptr<WebContentsObserver> prev_ GUARDED_BY_CONTEXT(sequence_checker_);
- raw_ptr<WebContentsObserver> next_ GUARDED_BY_CONTEXT(sequence_checker_);
- enum class LoadingState {
- // Initial state.
- // DidStartLoading(): Transition to kWaitingForNavigation.
- // PrimaryPageChanged(): Invalid from this state.
- // DidStopLoading(): Invalid from this state.
- kNotLoading,
- // DidStartLoading(): Invalid from this state.
- // PrimaryPageChanged(): Transition to kLoading.
- // DidStopLoading(): Transition to kNotLoading.
- kWaitingForNavigation,
- // DidStartLoading(): Invalid from this state.
- // PrimaryPageChanged(): Invalid from this state.
- // DidStopLoading(): Transition to kNotLoading.
- kLoading,
- };
- LoadingState loading_state_ GUARDED_BY_CONTEXT(sequence_checker_) =
- LoadingState::kNotLoading;
- SEQUENCE_CHECKER(sequence_checker_);
- };
- PageLoadTrackerDecoratorHelper::PageLoadTrackerDecoratorHelper() {
- PerformanceManager::AddObserver(this);
- }
- PageLoadTrackerDecoratorHelper::~PageLoadTrackerDecoratorHelper() {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- // Destroy all WebContentsObserver to ensure that PageLiveStateDecorators are
- // no longer maintained.
- while (first_web_contents_observer_)
- first_web_contents_observer_->DetachAndDestroy();
- PerformanceManager::RemoveObserver(this);
- }
- void PageLoadTrackerDecoratorHelper::OnPageNodeCreatedForWebContents(
- content::WebContents* web_contents) {
- DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- DCHECK(web_contents);
- // Start observing the WebContents. See comment on
- // |first_web_contents_observer_| for lifetime management details.
- new WebContentsObserver(web_contents, this);
- }
- } // namespace performance_manager
|