Commit 5b424b67 authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Create TabLoadTracker.

This is a helper component for tracking unloaded/loading/loaded state of a tab,
abstracting away the logic from TabManager internals, and exposing it for use by
external components. A follow-up CL will modify TabLoader to use it

BUG=749785

Change-Id: I2edf254fdba190d2822f470257ea2226b459290e
Reviewed-on: https://chromium-review.googlesource.com/938970
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarSébastien Marchand <sebmarchand@chromium.org>
Cr-Commit-Position: refs/heads/master@{#548185}
parent df7973c4
...@@ -1254,6 +1254,8 @@ jumbo_split_static_library("browser") { ...@@ -1254,6 +1254,8 @@ jumbo_split_static_library("browser") {
"resource_coordinator/resource_coordinator_render_process_probe.h", "resource_coordinator/resource_coordinator_render_process_probe.h",
"resource_coordinator/resource_coordinator_web_contents_observer.cc", "resource_coordinator/resource_coordinator_web_contents_observer.cc",
"resource_coordinator/resource_coordinator_web_contents_observer.h", "resource_coordinator/resource_coordinator_web_contents_observer.h",
"resource_coordinator/tab_load_tracker.cc",
"resource_coordinator/tab_load_tracker.h",
"resources_util.cc", "resources_util.cc",
"resources_util.h", "resources_util.h",
"safe_search_api/safe_search_url_checker.cc", "safe_search_api/safe_search_url_checker.cc",
......
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
#include "base/feature_list.h" #include "base/feature_list.h"
#include "base/strings/string_number_conversions.h" #include "base/strings/string_number_conversions.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/resource_coordinator/page_signal_receiver.h" #include "chrome/browser/resource_coordinator/page_signal_receiver.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/resource_coordinator/tab_manager.h"
#include "content/public/browser/navigation_handle.h" #include "content/public/browser/navigation_handle.h"
#include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h" #include "content/public/browser/render_process_host.h"
...@@ -23,6 +26,21 @@ ...@@ -23,6 +26,21 @@
#include "services/resource_coordinator/public/mojom/service_constants.mojom.h" #include "services/resource_coordinator/public/mojom/service_constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h" #include "services/service_manager/public/cpp/connector.h"
namespace {
// The manager currently doesn't exist on all platforms, which means the
// tab load tracker will not either.
// TODO(chrisha): Make the tab manager exist everywhere. It's going to start
// making scheduling decisions that apply to mobile devices as well, so there's
// no longer any reason for it to be mobile only.
resource_coordinator::TabLoadTracker* GetTabLoadTracker() {
if (auto* manager = g_browser_process->GetTabManager())
return &(manager->tab_load_tracker());
return nullptr;
}
} // namespace
DEFINE_WEB_CONTENTS_USER_DATA_KEY(ResourceCoordinatorWebContentsObserver); DEFINE_WEB_CONTENTS_USER_DATA_KEY(ResourceCoordinatorWebContentsObserver);
ResourceCoordinatorWebContentsObserver::ResourceCoordinatorWebContentsObserver( ResourceCoordinatorWebContentsObserver::ResourceCoordinatorWebContentsObserver(
...@@ -52,6 +70,9 @@ ResourceCoordinatorWebContentsObserver::ResourceCoordinatorWebContentsObserver( ...@@ -52,6 +70,9 @@ ResourceCoordinatorWebContentsObserver::ResourceCoordinatorWebContentsObserver(
page_signal_receiver->AssociateCoordinationUnitIDWithWebContents( page_signal_receiver->AssociateCoordinationUnitIDWithWebContents(
page_resource_coordinator_->id(), web_contents); page_resource_coordinator_->id(), web_contents);
} }
if (auto* tracker = GetTabLoadTracker())
tracker->StartTracking(web_contents);
} }
ResourceCoordinatorWebContentsObserver:: ResourceCoordinatorWebContentsObserver::
...@@ -66,10 +87,29 @@ bool ResourceCoordinatorWebContentsObserver::IsEnabled() { ...@@ -66,10 +87,29 @@ bool ResourceCoordinatorWebContentsObserver::IsEnabled() {
void ResourceCoordinatorWebContentsObserver::DidStartLoading() { void ResourceCoordinatorWebContentsObserver::DidStartLoading() {
page_resource_coordinator_->SetIsLoading(true); page_resource_coordinator_->SetIsLoading(true);
if (auto* tracker = GetTabLoadTracker())
tracker->DidStartLoading(web_contents());
}
void ResourceCoordinatorWebContentsObserver::DidReceiveResponse() {
if (auto* tracker = GetTabLoadTracker())
tracker->DidReceiveResponse(web_contents());
} }
void ResourceCoordinatorWebContentsObserver::DidStopLoading() { void ResourceCoordinatorWebContentsObserver::DidStopLoading() {
page_resource_coordinator_->SetIsLoading(false); page_resource_coordinator_->SetIsLoading(false);
if (auto* tracker = GetTabLoadTracker())
tracker->DidStopLoading(web_contents());
}
void ResourceCoordinatorWebContentsObserver::DidFailLoad(
content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) {
if (auto* tracker = GetTabLoadTracker())
tracker->DidFailLoad(web_contents());
} }
void ResourceCoordinatorWebContentsObserver::OnVisibilityChanged( void ResourceCoordinatorWebContentsObserver::OnVisibilityChanged(
...@@ -87,6 +127,8 @@ void ResourceCoordinatorWebContentsObserver::WebContentsDestroyed() { ...@@ -87,6 +127,8 @@ void ResourceCoordinatorWebContentsObserver::WebContentsDestroyed() {
page_signal_receiver->RemoveCoordinationUnitID( page_signal_receiver->RemoveCoordinationUnitID(
page_resource_coordinator_->id()); page_resource_coordinator_->id());
} }
if (auto* tracker = GetTabLoadTracker())
tracker->StopTracking(web_contents());
} }
void ResourceCoordinatorWebContentsObserver::DidFinishNavigation( void ResourceCoordinatorWebContentsObserver::DidFinishNavigation(
......
...@@ -35,7 +35,12 @@ class ResourceCoordinatorWebContentsObserver ...@@ -35,7 +35,12 @@ class ResourceCoordinatorWebContentsObserver
// WebContentsObserver implementation. // WebContentsObserver implementation.
void DidStartLoading() override; void DidStartLoading() override;
void DidReceiveResponse() override;
void DidStopLoading() override; void DidStopLoading() override;
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override;
void OnVisibilityChanged(content::Visibility visibility) override; void OnVisibilityChanged(content::Visibility visibility) override;
void WebContentsDestroyed() override; void WebContentsDestroyed() override;
void DidFinishNavigation( void DidFinishNavigation(
......
// Copyright 2018 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/resource_coordinator/tab_load_tracker.h"
#include "base/stl_util.h"
#include "chrome/browser/ui/browser_tab_strip_tracker.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "services/resource_coordinator/public/cpp/resource_coordinator_features.h"
namespace resource_coordinator {
TabLoadTracker::TabLoadTracker() {}
TabLoadTracker::~TabLoadTracker() {}
TabLoadTracker::LoadingState TabLoadTracker::GetLoadingState(
content::WebContents* web_contents) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
DCHECK(it != tabs_.end());
return it->second.loading_state;
}
size_t TabLoadTracker::GetTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return tabs_.size();
}
size_t TabLoadTracker::GetTabCount(LoadingState loading_state) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[loading_state];
}
size_t TabLoadTracker::GetUnloadedTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[UNLOADED];
}
size_t TabLoadTracker::GetLoadingTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[LOADING];
}
size_t TabLoadTracker::GetLoadedTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[LOADED];
}
void TabLoadTracker::AddObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.AddObserver(observer);
}
void TabLoadTracker::RemoveObserver(Observer* observer) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
observers_.RemoveObserver(observer);
}
void TabLoadTracker::StartTracking(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::ContainsKey(tabs_, web_contents));
LoadingState loading_state = DetermineLoadingState(web_contents);
// Insert the tab, making sure it's state is consistent with the valid states
// documented in TransitionState.
WebContentsData data;
data.loading_state = loading_state;
if (data.loading_state == LOADING)
data.did_start_loading_seen = true;
tabs_.insert(std::make_pair(web_contents, data));
++state_counts_[data.loading_state];
for (Observer& observer : observers_)
observer.OnStartTracking(web_contents, loading_state);
}
void TabLoadTracker::StopTracking(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
DCHECK(it != tabs_.end());
auto loading_state = it->second.loading_state;
DCHECK_NE(0u, state_counts_[it->second.loading_state]);
--state_counts_[it->second.loading_state];
tabs_.erase(it);
for (Observer& observer : observers_)
observer.OnStopTracking(web_contents, loading_state);
}
void TabLoadTracker::DidStartLoading(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!web_contents->IsLoadingToDifferentDocument())
return;
auto it = tabs_.find(web_contents);
DCHECK(it != tabs_.end());
if (it->second.loading_state == LOADING) {
DCHECK(it->second.did_start_loading_seen);
return;
}
it->second.did_start_loading_seen = true;
}
void TabLoadTracker::DidReceiveResponse(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
DCHECK(it != tabs_.end());
if (it->second.loading_state == LOADING) {
DCHECK(it->second.did_start_loading_seen);
return;
}
// A transition to loading requires both DidStartLoading (navigation
// committed) and DidReceiveResponse (data has been trasmitted over the
// network) events to occur. This is because NavigationThrottles can block
// actual network requests, but not the rest of the state machinery.
if (!it->second.did_start_loading_seen)
return;
TransitionState(it, LOADING);
}
void TabLoadTracker::DidStopLoading(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (resource_coordinator::IsPageAlmostIdleSignalEnabled())
return;
MaybeTransitionToLoaded(web_contents);
}
void TabLoadTracker::DidFailLoad(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
MaybeTransitionToLoaded(web_contents);
}
void TabLoadTracker::OnPageAlmostIdle(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(resource_coordinator::IsPageAlmostIdleSignalEnabled());
// PageAlmostIdle signals can be arbitrarily delayed as they are asynchronous.
// As such, they can arrive after the web contents in question no longer
// exists.
if (!base::ContainsKey(tabs_, web_contents))
return;
MaybeTransitionToLoaded(web_contents);
}
TabLoadTracker::LoadingState TabLoadTracker::DetermineLoadingState(
content::WebContents* web_contents) {
// Determine if the WebContents is actively loading, using our definition of
// loading. Start from the assumption that it is UNLOADED.
LoadingState loading_state = UNLOADED;
if (web_contents->IsLoadingToDifferentDocument() &&
!web_contents->IsWaitingForResponse()) {
loading_state = LOADING;
} else {
// Determine if the WebContents is already loaded. A loaded WebContents has
// a committed navigation entry, is not in an initial navigation, and
// doesn't require a reload. This can occur during prerendering, when an
// already rendered WebContents is swapped in at the moment of a navigation.
content::NavigationController& controller = web_contents->GetController();
if (controller.GetLastCommittedEntry() != nullptr &&
!controller.IsInitialNavigation() && !controller.NeedsReload()) {
loading_state = LOADED;
}
}
return loading_state;
}
void TabLoadTracker::MaybeTransitionToLoaded(
content::WebContents* web_contents) {
auto it = tabs_.find(web_contents);
DCHECK(it != tabs_.end());
if (it->second.loading_state != LOADING)
return;
TransitionState(it, LOADED);
}
void TabLoadTracker::TransitionState(TabMap::iterator it,
LoadingState loading_state) {
#if DCHECK_IS_ON()
// Validate the transition.
switch (loading_state) {
case LOADING: {
DCHECK_NE(LOADING, it->second.loading_state);
DCHECK(it->second.did_start_loading_seen);
break;
}
case LOADED: {
DCHECK_EQ(LOADING, it->second.loading_state);
DCHECK(it->second.did_start_loading_seen);
break;
}
case UNLOADED: // It never makes sense to transition to UNLOADED.
case LOADING_STATE_MAX:
NOTREACHED();
}
#endif
--state_counts_[it->second.loading_state];
it->second.loading_state = loading_state;
++state_counts_[loading_state];
// If the destination state is LOADED, then also clear the
// |did_start_loading_seen| state.
if (loading_state == LOADED)
it->second.did_start_loading_seen = false;
for (Observer& observer : observers_)
observer.OnLoadingStateChange(it->first, loading_state);
}
TabLoadTracker::Observer::Observer() {}
TabLoadTracker::Observer::~Observer() {}
} // namespace resource_coordinator
// Copyright 2018 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.
#ifndef CHROME_BROWSER_RESOURCE_COORDINATOR_TAB_LOAD_TRACKER_H_
#define CHROME_BROWSER_RESOURCE_COORDINATOR_TAB_LOAD_TRACKER_H_
#include "base/callback.h"
#include "base/containers/flat_map.h"
#include "base/macros.h"
#include "base/observer_list.h"
#include "base/sequence_checker.h"
#include "base/strings/string16.h"
class ResourceCoordinatorWebContentsObserver;
namespace content {
class WebContents;
} // namespace content
namespace resource_coordinator {
class TabManagerResourceCoordinatorSignalObserverHelper;
// This class has the sole purpose of tracking the state of all tab-related
// WebContents, and whether or not they are in an unloaded, currently loading,
// or loaded state.
//
// This class must be bound to a given Sequence and all access to it must
// occur on that Sequence. In practice, this is intended to be on the UI
// thread as the notifications of interest occur natively on that thread. All
// calculations are very short and quick, so it is suitable for use on that
// thread.
//
// This class is intended to be created in early startup and persists as a
// singleton in the browser process, owned by the TabManager. It is deliberately
// leaked at shutdown.
//
// This class isn't directly an observer of anything. An external source must
// invoke the callbacks in the protected section of the class. In the case of
// the TabManager this is done by a combination of the
// ResourceCoordinatorWebContentsObserver and the
// TabManagerResourceCoordinatorSignalObserver.
class TabLoadTracker {
public:
// An observer class. This allows external classes to be notified of loading
// state changes.
class Observer;
// Indicates the loading state of a WebContents.
enum LoadingState {
// An initially constructed WebContents with no loaded content is UNLOADED.
// A WebContents that started loading but that errored out before receiving
// sufficient content to render is also considered UNLOADED.
// Can only transition from here to LOADING.
UNLOADED,
// A WebContents with an ongoing main-frame navigation (to a new document)
// is in a loading state. More precisely, it is considered loading once
// network data has started to be transmitted, and not simply when the
// navigation has started. This considers throttled navigations as not yet
// loading, and will only transition to loading once the throttle has been
// removed.
// Can transition from here to UNLOADED or LOADED.
LOADING,
// A WebContents with a committed navigation whose
// DidStopLoading/PageAlmostIdle event (depending on mode) or DidFailLoad
// event has fired is no longer considered to be LOADING. If any content has
// been rendered prior to the failure the document is considered LOADED,
// otherwise it is considered UNLOADED.
// Can transition from here to LOADING.
LOADED,
// This must be last.
LOADING_STATE_MAX
};
TabLoadTracker();
~TabLoadTracker();
// Allows querying the state of a tab. The provided |web_contents| must be
// actively tracked.
LoadingState GetLoadingState(content::WebContents* web_contents) const;
// Returns the total number of tabs that are being tracked by this class.
size_t GetTabCount() const;
// Returns the number of tabs in each state.
size_t GetTabCount(LoadingState loading_state) const;
size_t GetUnloadedTabCount() const;
size_t GetLoadingTabCount() const;
size_t GetLoadedTabCount() const;
// Adds/removes an observer. It is up to the observer to ensure their lifetime
// exceeds that of the TabLoadTracker, as is removed prior to its destruction.
void AddObserver(Observer* observer);
void RemoveObserver(Observer* observer);
protected:
// This allows the various bits of TabManager plubming to forward
// notifications to the TabLoadTracker.
friend class ::ResourceCoordinatorWebContentsObserver;
friend class ::resource_coordinator::
TabManagerResourceCoordinatorSignalObserverHelper;
// Initiates tracking of a WebContents. This is fully able to determine the
// initial state of the WebContents, even if it was created long ago
// (is LOADING or LOADED) and only just attached to the tracker. See the
// implementation of DetermineLoadingState for details.
void StartTracking(content::WebContents* web_contents);
// Stops tracking a |web_contents|.
void StopTracking(content::WebContents* web_contents);
// These are analogs of WebContentsObserver functions. This class is not
// actually an observer, but the relevant events are forwarded to it from
// the TabManager.
void DidStartLoading(content::WebContents* web_contents);
void DidReceiveResponse(content::WebContents* web_contents);
void DidStopLoading(content::WebContents* web_contents);
void DidFailLoad(content::WebContents* web_contents);
// This is an analog of a PageSignalObserver function. This class is not
// actually a PageSignalObserver, but these notifications are forwarded to it
// from the TabManager.
void OnPageAlmostIdle(content::WebContents* web_contents);
private:
// For unittesting.
friend class TestTabLoadTracker;
// Some metadata used to track the current state of the WebContents.
struct WebContentsData {
LoadingState loading_state = UNLOADED;
bool did_start_loading_seen = false;
};
using TabMap = base::flat_map<content::WebContents*, WebContentsData>;
// Helper function for determining the current state of a |web_contents|.
LoadingState DetermineLoadingState(content::WebContents* web_contents);
// Helper function for marking a load as finished, if possible. If the tab
// isn't currently marked as loading then this does nothing.
void MaybeTransitionToLoaded(content::WebContents* web_contents);
// Transitions a web contents to the given state. This updates the various
// |state_counts_| and |tabs_| data.
void TransitionState(TabMap::iterator it, LoadingState loading_state);
// The list of known WebContents and their states.
TabMap tabs_;
// The counts of tabs in each state.
size_t state_counts_[LOADING_STATE_MAX] = {0};
base::ObserverList<Observer> observers_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(TabLoadTracker);
};
// A class for observing loading state changes of WebContents under observation
// by a given TabLoadTracker. All of the callbacks will be invoked on the
// sequence to which the TabLoadTracker is bound.
class TabLoadTracker::Observer {
public:
using LoadingState = TabLoadTracker::LoadingState;
Observer();
virtual ~Observer();
// Called when a |web_contents| is starting to be tracked.
virtual void OnStartTracking(content::WebContents* web_contents,
LoadingState loading_state) {}
// Called for every loading state change observed on a |web_contents|.
virtual void OnLoadingStateChange(content::WebContents* web_contents,
LoadingState loading_state) {}
// Called when a |web_contents| is no longer being tracked.
virtual void OnStopTracking(content::WebContents* web_contents,
LoadingState loading_state) {}
private:
DISALLOW_COPY_AND_ASSIGN(Observer);
};
} // namespace resource_coordinator
#endif // CHROME_BROWSER_RESOURCE_COORDINATOR_TAB_LOAD_TRACKER_H_
// Copyright 2018 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/resource_coordinator/tab_load_tracker.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/test/web_contents_tester.h"
#include "services/resource_coordinator/public/cpp/resource_coordinator_features.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace resource_coordinator {
using testing::_;
using testing::StrictMock;
// Test wrapper of TabLoadTracker that exposes some internals.
class TestTabLoadTracker : public TabLoadTracker {
public:
using TabLoadTracker::StartTracking;
using TabLoadTracker::StopTracking;
using TabLoadTracker::DidStartLoading;
using TabLoadTracker::DidReceiveResponse;
using TabLoadTracker::DidStopLoading;
using TabLoadTracker::DidFailLoad;
using TabLoadTracker::OnPageAlmostIdle;
using TabLoadTracker::DetermineLoadingState;
TestTabLoadTracker() {}
virtual ~TestTabLoadTracker() {}
// Some accessors for TabLoadTracker internals.
const TabMap& tabs() const { return tabs_; }
// Determines if the tab has been marked as having received the
// DidStartLoading event.
bool DidStartLoadingSeen(content::WebContents* web_contents) {
auto it = tabs_.find(web_contents);
if (it == tabs_.end())
return false;
return it->second.did_start_loading_seen;
}
};
// A mock observer class.
class LenientMockObserver : public TabLoadTracker::Observer {
public:
LenientMockObserver() {}
~LenientMockObserver() override {}
// TabLoadTracker::Observer implementation:
MOCK_METHOD2(OnStartTracking, void(content::WebContents*, LoadingState));
MOCK_METHOD2(OnLoadingStateChange, void(content::WebContents*, LoadingState));
MOCK_METHOD2(OnStopTracking, void(content::WebContents*, LoadingState));
private:
DISALLOW_COPY_AND_ASSIGN(LenientMockObserver);
};
using MockObserver = testing::StrictMock<LenientMockObserver>;
// A WebContentsObserver that forwards relevant WebContents events to the
// provided tracker.
class TestWebContentsObserver : public content::WebContentsObserver {
public:
TestWebContentsObserver(content::WebContents* web_contents,
TestTabLoadTracker* tracker)
: content::WebContentsObserver(web_contents), tracker_(tracker) {}
~TestWebContentsObserver() override {}
// content::WebContentsObserver:
void DidStartLoading() override { tracker_->DidStartLoading(web_contents()); }
void DidReceiveResponse() override {
tracker_->DidReceiveResponse(web_contents());
}
void DidStopLoading() override { tracker_->DidStopLoading(web_contents()); }
void DidFailLoad(content::RenderFrameHost* render_frame_host,
const GURL& validated_url,
int error_code,
const base::string16& error_description) override {
tracker_->DidFailLoad(web_contents());
}
private:
TestTabLoadTracker* tracker_;
};
// The test harness.
class TabLoadTrackerTest : public ChromeRenderViewHostTestHarness {
public:
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
contents1_.reset(CreateTestWebContents());
contents2_.reset(CreateTestWebContents());
contents3_.reset(CreateTestWebContents());
tracker_.AddObserver(&observer_);
}
void TearDown() override {
// The WebContents must be deleted before the test harness deletes the
// RenderProcessHost.
contents1_.reset();
contents2_.reset();
contents3_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
// Enables the PAI feature so that the TabLoadTracker can be tested in both
// modes.
void EnablePAI() {
feature_list_ = std::make_unique<base::test::ScopedFeatureList>();
feature_list_->InitAndEnableFeature(features::kPageAlmostIdle);
ASSERT_TRUE(resource_coordinator::IsPageAlmostIdleSignalEnabled());
}
void ExpectTabCounts(size_t tabs,
size_t unloaded,
size_t loading,
size_t loaded) {
EXPECT_EQ(tabs, unloaded + loading + loaded);
EXPECT_EQ(tabs, tracker().GetTabCount());
EXPECT_EQ(unloaded, tracker().GetUnloadedTabCount());
EXPECT_EQ(loading, tracker().GetLoadingTabCount());
EXPECT_EQ(loaded, tracker().GetLoadedTabCount());
}
void StateTransitionsTest(bool enable_pai);
TestTabLoadTracker& tracker() { return tracker_; }
MockObserver& observer() { return observer_; }
content::WebContents* contents1() { return contents1_.get(); }
content::WebContents* contents2() { return contents2_.get(); }
content::WebContents* contents3() { return contents3_.get(); }
private:
TestTabLoadTracker tracker_;
std::unique_ptr<base::test::ScopedFeatureList> feature_list_;
MockObserver observer_;
std::unique_ptr<content::WebContents> contents1_;
std::unique_ptr<content::WebContents> contents2_;
std::unique_ptr<content::WebContents> contents3_;
};
// A macro that ensures that a meaningful line number gets included in the
// stack trace when ExpectTabCounts fails.
#define EXPECT_TAB_COUNTS(a, b, c, d) \
{ \
SCOPED_TRACE(""); \
ExpectTabCounts(a, b, c, d); \
}
TEST_F(TabLoadTrackerTest, DetermineLoadingState) {
auto* tester1 = content::WebContentsTester::For(contents1());
EXPECT_EQ(TestTabLoadTracker::UNLOADED,
tracker().DetermineLoadingState(contents1()));
// Navigate to a page and expect it to be loading.
tester1->NavigateAndCommit(GURL("http://chromium.org"));
EXPECT_EQ(TestTabLoadTracker::LOADING,
tracker().DetermineLoadingState(contents1()));
// Indicate that loading is finished and expect the state to transition.
tester1->TestSetIsLoading(false);
EXPECT_EQ(TestTabLoadTracker::LOADED,
tracker().DetermineLoadingState(contents1()));
}
void TabLoadTrackerTest::StateTransitionsTest(bool enable_pai) {
if (enable_pai)
EnablePAI();
auto* tester1 = content::WebContentsTester::For(contents1());
auto* tester2 = content::WebContentsTester::For(contents2());
auto* tester3 = content::WebContentsTester::For(contents3());
// Set up the contents in UNLOADED, LOADING and LOADED states. This tests
// each possible "entry" state.
tester2->NavigateAndCommit(GURL("http://foo.com"));
tester3->NavigateAndCommit(GURL("http://bar.com"));
tester3->TestSetIsLoading(false);
// Add the contents to the trackers.
EXPECT_CALL(observer(),
OnStartTracking(contents1(), TestTabLoadTracker::UNLOADED));
tracker().StartTracking(contents1());
EXPECT_TAB_COUNTS(1, 1, 0, 0);
testing::Mock::VerifyAndClearExpectations(&observer());
EXPECT_CALL(observer(),
OnStartTracking(contents2(), TestTabLoadTracker::LOADING));
tracker().StartTracking(contents2());
EXPECT_TAB_COUNTS(2, 1, 1, 0);
testing::Mock::VerifyAndClearExpectations(&observer());
EXPECT_CALL(observer(),
OnStartTracking(contents3(), TestTabLoadTracker::LOADED));
tracker().StartTracking(contents3());
EXPECT_TAB_COUNTS(3, 1, 1, 1);
testing::Mock::VerifyAndClearExpectations(&observer());
// Start observers for the contents.
TestWebContentsObserver observer1(contents1(), &tracker());
TestWebContentsObserver observer2(contents2(), &tracker());
TestWebContentsObserver observer3(contents3(), &tracker());
// Now test all of the possible state transitions.
// Finish the loading for contents2.
EXPECT_CALL(observer(),
OnLoadingStateChange(contents2(), TestTabLoadTracker::LOADED));
tester2->TestSetIsLoading(false);
if (enable_pai) {
// The state transition should only occur *after* the PAI signal when that
// feature is enabled.
EXPECT_TAB_COUNTS(3, 1, 1, 1);
tracker().OnPageAlmostIdle(contents2());
}
EXPECT_TAB_COUNTS(3, 1, 0, 2);
testing::Mock::VerifyAndClearExpectations(&observer());
// Start the loading for contents1.
EXPECT_CALL(observer(),
OnLoadingStateChange(contents1(), TestTabLoadTracker::LOADING));
tester1->NavigateAndCommit(GURL("http://baz.com"));
EXPECT_TAB_COUNTS(3, 0, 1, 2);
testing::Mock::VerifyAndClearExpectations(&observer());
// Stop the loading with an error. The tab should go back to a LOADED
// state.
EXPECT_CALL(observer(),
OnLoadingStateChange(contents1(), TestTabLoadTracker::LOADED));
tester1->TestDidFailLoadWithError(GURL("http://baz.com"), 500,
base::UTF8ToUTF16("server error"));
ExpectTabCounts(3, 0, 0, 3);
testing::Mock::VerifyAndClearExpectations(&observer());
}
TEST_F(TabLoadTrackerTest, StateTransitions) {
StateTransitionsTest(false);
}
TEST_F(TabLoadTrackerTest, StateTransitionsPAI) {
StateTransitionsTest(true);
}
} // namespace resource_coordinator
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h" #include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h" #include "chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h" #include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/sessions/session_restore_observer.h" #include "chrome/browser/sessions/session_restore_observer.h"
#include "chrome/browser/ui/browser_tab_strip_tracker.h" #include "chrome/browser/ui/browser_tab_strip_tracker.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h" #include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
...@@ -205,6 +206,11 @@ class TabManager : public LifecycleUnitObserver, ...@@ -205,6 +206,11 @@ class TabManager : public LifecycleUnitObserver,
// non-zero only during session restore. // non-zero only during session restore.
int restored_tab_count() const; int restored_tab_count() const;
// Accessor for the tab load tracker. This lets interested external classes
// add themselves as observers.
TabLoadTracker& tab_load_tracker() { return tab_load_tracker_; }
const TabLoadTracker& tab_load_tracker() const { return tab_load_tracker_; }
// Duration during which a tab cannot be automatically discarded after having // Duration during which a tab cannot be automatically discarded after having
// been active. // been active.
static constexpr base::TimeDelta kDiscardProtectionTime = static constexpr base::TimeDelta kDiscardProtectionTime =
...@@ -472,6 +478,10 @@ class TabManager : public LifecycleUnitObserver, ...@@ -472,6 +478,10 @@ class TabManager : public LifecycleUnitObserver,
// session restore. // session restore.
std::unique_ptr<TabManagerStatsCollector> stats_collector_; std::unique_ptr<TabManagerStatsCollector> stats_collector_;
// Tracks tab loads, taking into account PageAlmostIdle, NavigationThrottles
// and other complications.
TabLoadTracker tab_load_tracker_;
// Weak pointer factory used for posting delayed tasks. // Weak pointer factory used for posting delayed tasks.
base::WeakPtrFactory<TabManager> weak_ptr_factory_; base::WeakPtrFactory<TabManager> weak_ptr_factory_;
......
...@@ -6,11 +6,30 @@ ...@@ -6,11 +6,30 @@
#include "base/time/time.h" #include "base/time/time.h"
#include "chrome/browser/browser_process.h" #include "chrome/browser/browser_process.h"
#include "chrome/browser/resource_coordinator/tab_load_tracker.h"
#include "chrome/browser/resource_coordinator/tab_manager_stats_collector.h" #include "chrome/browser/resource_coordinator/tab_manager_stats_collector.h"
#include "chrome/browser/resource_coordinator/tab_manager_web_contents_data.h" #include "chrome/browser/resource_coordinator/tab_manager_web_contents_data.h"
namespace resource_coordinator { namespace resource_coordinator {
namespace {
TabLoadTracker& GetTabLoadTracker() {
return g_browser_process->GetTabManager()->tab_load_tracker();
}
} // namespace
// A helper class for accessing TabLoadTracker. TabLoadTracker can't directly
// friend TabManager::ResourceCoordinatorSignalObserver as it's a nested class
// and can't be forward declared.
class TabManagerResourceCoordinatorSignalObserverHelper {
public:
static void OnPageAlmostIdle(content::WebContents* web_contents) {
GetTabLoadTracker().OnPageAlmostIdle(web_contents);
}
};
TabManager::ResourceCoordinatorSignalObserver:: TabManager::ResourceCoordinatorSignalObserver::
ResourceCoordinatorSignalObserver() { ResourceCoordinatorSignalObserver() {
if (auto* page_signal_receiver = PageSignalReceiver::GetInstance()) if (auto* page_signal_receiver = PageSignalReceiver::GetInstance())
...@@ -25,6 +44,8 @@ TabManager::ResourceCoordinatorSignalObserver:: ...@@ -25,6 +44,8 @@ TabManager::ResourceCoordinatorSignalObserver::
void TabManager::ResourceCoordinatorSignalObserver::OnPageAlmostIdle( void TabManager::ResourceCoordinatorSignalObserver::OnPageAlmostIdle(
content::WebContents* web_contents) { content::WebContents* web_contents) {
TabManagerResourceCoordinatorSignalObserverHelper::OnPageAlmostIdle(
web_contents);
auto* web_contents_data = auto* web_contents_data =
TabManager::WebContentsData::FromWebContents(web_contents); TabManager::WebContentsData::FromWebContents(web_contents);
if (!web_contents_data) if (!web_contents_data)
......
...@@ -2907,6 +2907,7 @@ test("unit_tests") { ...@@ -2907,6 +2907,7 @@ test("unit_tests") {
"../browser/resource_coordinator/tab_activity_watcher_unittest.cc", "../browser/resource_coordinator/tab_activity_watcher_unittest.cc",
"../browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc", "../browser/resource_coordinator/tab_lifecycle_unit_source_unittest.cc",
"../browser/resource_coordinator/tab_lifecycle_unit_unittest.cc", "../browser/resource_coordinator/tab_lifecycle_unit_unittest.cc",
"../browser/resource_coordinator/tab_load_tracker_unittest.cc",
"../browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc", "../browser/resource_coordinator/tab_manager_delegate_chromeos_unittest.cc",
"../browser/resource_coordinator/tab_manager_features_unittest.cc", "../browser/resource_coordinator/tab_manager_features_unittest.cc",
"../browser/resource_coordinator/tab_manager_stats_collector_unittest.cc", "../browser/resource_coordinator/tab_manager_stats_collector_unittest.cc",
......
...@@ -146,6 +146,12 @@ class WebContentsTester { ...@@ -146,6 +146,12 @@ class WebContentsTester {
// Simulates a direct user interaction. // Simulates a direct user interaction.
// |render_widget_host| can be equal to nullptr. // |render_widget_host| can be equal to nullptr.
virtual void TestOnUserInteraction(blink::WebInputEvent::Type type) = 0; virtual void TestOnUserInteraction(blink::WebInputEvent::Type type) = 0;
// Simulates terminating an load with a network error.
virtual void TestDidFailLoadWithError(
const GURL& url,
int error_code,
const base::string16& error_description) = 0;
}; };
} // namespace content } // namespace content
......
...@@ -193,6 +193,36 @@ void TestWebContents::SetLastCommittedURL(const GURL& url) { ...@@ -193,6 +193,36 @@ void TestWebContents::SetLastCommittedURL(const GURL& url) {
last_committed_url_ = url; last_committed_url_ = url;
} }
void TestWebContents::SetMainFrameMimeType(const std::string& mime_type) {
WebContentsImpl::SetMainFrameMimeType(mime_type);
}
void TestWebContents::SetWasRecentlyAudible(bool audible) {
audio_stream_monitor()->set_was_recently_audible_for_testing(audible);
}
void TestWebContents::SetIsCurrentlyAudible(bool audible) {
audio_stream_monitor()->set_is_currently_audible_for_testing(audible);
}
void TestWebContents::TestOnUserInteraction(blink::WebInputEvent::Type type) {
// Use the first RenderWidgetHost from the frame tree to make sure that the
// interaction doesn't get ignored.
DCHECK(frame_tree_.Nodes().begin() != frame_tree_.Nodes().end());
RenderWidgetHostImpl* render_widget_host = (*frame_tree_.Nodes().begin())
->current_frame_host()
->GetRenderWidgetHost();
OnUserInteraction(render_widget_host, type);
}
void TestWebContents::TestDidFailLoadWithError(
const GURL& url,
int error_code,
const base::string16& error_description) {
FrameHostMsg_DidFailLoadWithError msg(0, url, error_code, error_description);
frame_tree_.root()->current_frame_host()->OnMessageReceived(msg);
}
bool TestWebContents::CrossProcessNavigationPending() { bool TestWebContents::CrossProcessNavigationPending() {
return GetRenderManager()->speculative_render_frame_host_ != nullptr; return GetRenderManager()->speculative_render_frame_host_ != nullptr;
} }
...@@ -326,14 +356,6 @@ void TestWebContents::TestDidFinishLoad(const GURL& url) { ...@@ -326,14 +356,6 @@ void TestWebContents::TestDidFinishLoad(const GURL& url) {
frame_tree_.root()->current_frame_host()->OnMessageReceived(msg); frame_tree_.root()->current_frame_host()->OnMessageReceived(msg);
} }
void TestWebContents::TestDidFailLoadWithError(
const GURL& url,
int error_code,
const base::string16& error_description) {
FrameHostMsg_DidFailLoadWithError msg(0, url, error_code, error_description);
frame_tree_.root()->current_frame_host()->OnMessageReceived(msg);
}
void TestWebContents::SetNavigationData( void TestWebContents::SetNavigationData(
NavigationHandle* navigation_handle, NavigationHandle* navigation_handle,
std::unique_ptr<NavigationData> navigation_data) { std::unique_ptr<NavigationData> navigation_data) {
...@@ -390,26 +412,4 @@ void TestWebContents::SaveFrameWithHeaders( ...@@ -390,26 +412,4 @@ void TestWebContents::SaveFrameWithHeaders(
suggested_filename_ = suggested_filename; suggested_filename_ = suggested_filename;
} }
void TestWebContents::SetMainFrameMimeType(const std::string& mime_type) {
WebContentsImpl::SetMainFrameMimeType(mime_type);
}
void TestWebContents::SetWasRecentlyAudible(bool audible) {
audio_stream_monitor()->set_was_recently_audible_for_testing(audible);
}
void TestWebContents::SetIsCurrentlyAudible(bool audible) {
audio_stream_monitor()->set_is_currently_audible_for_testing(audible);
}
void TestWebContents::TestOnUserInteraction(blink::WebInputEvent::Type type) {
// Use the first RenderWidgetHost from the frame tree to make sure that the
// interaction doesn't get ignored.
DCHECK(frame_tree_.Nodes().begin() != frame_tree_.Nodes().end());
RenderWidgetHostImpl* render_widget_host = (*frame_tree_.Nodes().begin())
->current_frame_host()
->GetRenderWidgetHost();
OnUserInteraction(render_widget_host, type);
}
} // namespace content } // namespace content
...@@ -98,6 +98,10 @@ class TestWebContents : public WebContentsImpl, public WebContentsTester { ...@@ -98,6 +98,10 @@ class TestWebContents : public WebContentsImpl, public WebContentsTester {
void SetWasRecentlyAudible(bool audible) override; void SetWasRecentlyAudible(bool audible) override;
void SetIsCurrentlyAudible(bool audible) override; void SetIsCurrentlyAudible(bool audible) override;
void TestOnUserInteraction(blink::WebInputEvent::Type type) override; void TestOnUserInteraction(blink::WebInputEvent::Type type) override;
void TestDidFailLoadWithError(
const GURL& url,
int error_code,
const base::string16& error_description) override;
// True if a cross-site navigation is pending. // True if a cross-site navigation is pending.
bool CrossProcessNavigationPending(); bool CrossProcessNavigationPending();
...@@ -136,9 +140,6 @@ class TestWebContents : public WebContentsImpl, public WebContentsTester { ...@@ -136,9 +140,6 @@ class TestWebContents : public WebContentsImpl, public WebContentsTester {
int history_length) override; int history_length) override;
void TestDidFinishLoad(const GURL& url); void TestDidFinishLoad(const GURL& url);
void TestDidFailLoadWithError(const GURL& url,
int error_code,
const base::string16& error_description);
protected: protected:
// The deprecated WebContentsTester still needs to subclass this. // The deprecated WebContentsTester still needs to subclass this.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment