1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245
|
// Copyright 2018 The Chromium Authors
// 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 <utility>
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/observer_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/resource_coordinator/resource_coordinator_parts.h"
#include "components/no_state_prefetch/browser/no_state_prefetch_contents.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"
namespace resource_coordinator {
namespace {
static constexpr TabLoadTracker::LoadingState UNLOADED =
TabLoadTracker::LoadingState::UNLOADED;
static constexpr TabLoadTracker::LoadingState LOADING =
TabLoadTracker::LoadingState::LOADING;
static constexpr TabLoadTracker::LoadingState LOADED =
TabLoadTracker::LoadingState::LOADED;
} // namespace
TabLoadTracker::~TabLoadTracker() = default;
// static
TabLoadTracker* TabLoadTracker::Get() {
DCHECK(g_browser_process);
return g_browser_process->resource_coordinator_parts()->tab_load_tracker();
}
TabLoadTracker::LoadingState TabLoadTracker::GetLoadingState(
content::WebContents* web_contents) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
CHECK(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_[static_cast<size_t>(loading_state)];
}
size_t TabLoadTracker::GetUnloadedTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[static_cast<size_t>(UNLOADED)];
}
size_t TabLoadTracker::GetLoadingTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[static_cast<size_t>(LOADING)];
}
size_t TabLoadTracker::GetLoadedTabCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return state_counts_[static_cast<size_t>(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::TransitionStateForTesting(
content::WebContents* web_contents,
LoadingState loading_state) {
auto it = tabs_.find(web_contents);
CHECK(it != tabs_.end());
TransitionState(it, loading_state);
}
TabLoadTracker::TabLoadTracker() = default;
void TabLoadTracker::StartTracking(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!base::Contains(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;
tabs_.insert(std::make_pair(web_contents, data));
++state_counts_[static_cast<size_t>(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);
CHECK(it != tabs_.end());
auto loading_state = it->second.loading_state;
DCHECK_NE(0u, state_counts_[static_cast<size_t>(it->second.loading_state)]);
--state_counts_[static_cast<size_t>(it->second.loading_state)];
tabs_.erase(it);
for (Observer& observer : observers_)
observer.OnStopTracking(web_contents, loading_state);
}
void TabLoadTracker::PrimaryPageChanged(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Only observe top-level navigation that triggers navigation UI.
if (!web_contents->ShouldShowLoadingUI())
return;
auto it = tabs_.find(web_contents);
CHECK(it != tabs_.end());
TransitionState(it, LOADING);
}
void TabLoadTracker::DidStopLoading(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
CHECK(it != tabs_.end());
// Corner case: An unloaded tab that starts loading but never receives a
// response transitions to the LOADED state when loading stops, without
// traversing the LOADING state. This can happen when the server doesn't
// respond or when there is no network connection.
if (it->second.loading_state == LoadingState::UNLOADED)
TransitionState(it, LOADED);
}
void TabLoadTracker::WasDiscarded(content::WebContents* web_contents) {
TransitionToUnloaded(web_contents);
}
void TabLoadTracker::RenderProcessGone(content::WebContents* web_contents,
base::TerminationStatus status) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Don't bother tracking the UNLOADED state change for normal renderer
// shutdown, the |web_contents| will be untracked shortly.
if (status ==
base::TerminationStatus::TERMINATION_STATUS_NORMAL_TERMINATION ||
status == base::TerminationStatus::TERMINATION_STATUS_STILL_RUNNING) {
return;
}
// We reach here when a tab crashes, i.e. it's main frame renderer dies
// unexpectedly (sad tab). In this case there is still an associated
// WebContents, but it is not backed by a renderer. The renderer could have
// died because of a crash (e.g. bugs, compromised renderer) or been killed by
// the OS (e.g. OOM on Android). Note: discarded tabs may reach this method,
// but exit early because of |status|.
TransitionToUnloaded(web_contents);
}
void TabLoadTracker::OnPageStoppedLoading(content::WebContents* web_contents) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto it = tabs_.find(web_contents);
if (it == tabs_.end()) {
// The graph tracks objects that are not tracked by this object.
return;
}
TransitionState(it, LOADED);
}
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->ShouldShowLoadingUI() &&
!web_contents->IsWaitingForResponse()) {
loading_state = LOADING;
} else {
// Determine if the WebContents is already loaded. A loaded WebContents has
// a committed navigation entry that is not the initial 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()->IsInitialEntry() &&
!controller.IsInitialNavigation() && !controller.NeedsReload()) {
loading_state = LOADED;
}
}
return loading_state;
}
void TabLoadTracker::TransitionToUnloaded(content::WebContents* web_contents) {
auto it = tabs_.find(web_contents);
CHECK(it != tabs_.end());
// The tab could already be UNLOADED if it hasn't yet started loading. This
// can happen if the renderer crashes between the UNLOADED and LOADING states.
if (it->second.loading_state == UNLOADED) {
return;
}
TransitionState(it, UNLOADED);
}
void TabLoadTracker::TransitionState(TabMap::iterator it,
LoadingState loading_state) {
LoadingState previous_state = it->second.loading_state;
if (previous_state == loading_state) {
return;
}
--state_counts_[static_cast<size_t>(previous_state)];
it->second.loading_state = loading_state;
++state_counts_[static_cast<size_t>(loading_state)];
// Store |it->first| instead of passing it directly in the loop below in case
// an observer starts/stops tracking a WebContents and invalidates |it|.
content::WebContents* web_contents = it->first;
for (Observer& observer : observers_)
observer.OnLoadingStateChange(web_contents, previous_state, loading_state);
}
TabLoadTracker::Observer::Observer() = default;
TabLoadTracker::Observer::~Observer() = default;
} // namespace resource_coordinator
|