blob: 440fdf8df66efce1b68a0463189986a6a0b0353a [file] [log] [blame]
// Copyright (c) 2012 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 <stdint.h>
#include <memory>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/gtest_prod_util.h"
#include "base/macros.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/strings/string16.h"
#include "base/timer/timer.h"
#include "build/build_config.h"
#include "chrome/browser/resource_coordinator/discard_reason.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h"
#include "chrome/browser/resource_coordinator/tab_stats.h"
#include "chrome/browser/sessions/session_restore_observer.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_tab_strip_tracker.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "content/public/browser/navigation_throttle.h"
#include "ui/gfx/native_widget_types.h"
class BrowserList;
class GURL;
class TabStripModel;
namespace content {
class NavigationHandle;
class WebContents;
namespace resource_coordinator {
class BackgroundTabNavigationThrottle;
#if defined(OS_CHROMEOS)
class TabManagerDelegate;
class TabManagerStatsCollector;
// Information about a Browser.
struct BrowserInfo {
Browser* browser = nullptr; // Can be nullptr in tests.
TabStripModel* tab_strip_model = nullptr;
bool window_is_minimized = false;
bool browser_is_app = false;
// The TabManager periodically updates (see
// |kAdjustmentIntervalSeconds| in the source) the status of renderers
// which are then used by the algorithm embedded here for priority in being
// killed upon OOM conditions.
// The algorithm used favors killing tabs that are not active, not in an active
// window, not in a visible window, not pinned, and have been idle for longest,
// in that order of priority.
// On Chrome OS (via the delegate), the kernel (via /proc/<pid>/oom_score_adj)
// will be informed of each renderer's score, which is based on the status, so
// in case Chrome is not able to relieve the pressure quickly enough and the
// kernel is forced to kill processes, it will be able to do so using the same
// algorithm as the one used here.
// The TabManager also delays background tabs' navigation when needed in order
// to improve users' experience with the foreground tab.
// Note that the browser tests are only active for platforms that use
// TabManager (CrOS only for now) and need to be adjusted accordingly if
// support for new platforms is added.
// Tabs are identified by a unique ID vended by this component. These IDs are
// not reused in a session. They are stable for a given conceptual tab, and will
// follow it through discards, reloads, tab strip operations, etc.
class TabManager : public TabStripModelObserver, public BrowserListObserver {
// Forward declaration of resource coordinator signal observer.
class ResourceCoordinatorSignalObserver;
// Needs to be public for DEFINE_WEB_CONTENTS_USER_DATA_KEY.
class WebContentsData;
~TabManager() override;
// Number of discard events since Chrome started.
int discard_count() const { return discard_count_; }
// Start/Stop the Tab Manager.
void Start();
void Stop();
// Returns the list of the stats for all renderers. Must be called on the UI
// thread. The returned list is sorted by reversed importance.
TabStatsList GetTabStats() const;
// Returns true if |contents| is currently discarded.
bool IsTabDiscarded(content::WebContents* contents) const;
// Goes through a list of checks to see if a tab is allowed to be discarded
// for |reason|. Note that this is not used when discarding a particular tab
// from about:discards or from an extension.
bool CanDiscardTab(const TabStats& tab_stats, DiscardReason reason) const;
// Discards a tab to free the memory occupied by its renderer. The tab still
// exists in the tab-strip; clicking on it will reload it. If the |reason| is
// urgent, an aggressive fast-kill will be attempted if the sudden termination
// disablers are allowed to be ignored (e.g. On ChromeOS, we can ignore an
// unload handler and fast-kill the tab regardless).
void DiscardTab(DiscardReason reason);
// Discards a tab with the given unique ID. The tab still exists in the
// tab-strip; clicking on it will reload it. Returns null if the tab cannot
// be found or cannot be discarded. Otherwise returns the new web_contents
// of the discarded tab.
content::WebContents* DiscardTabById(int32_t tab_id, DiscardReason reason);
// Method used by the extensions API to discard tabs. If |contents| is null,
// discards the least important tab using DiscardTab(). Otherwise discards
// the given contents. Returns the new web_contents or null if no tab
// was discarded.
content::WebContents* DiscardTabByExtension(content::WebContents* contents);
// Log memory statistics for the running processes, then discards a tab.
// Tab discard happens sometime later, as collecting the statistics touches
// multiple threads and takes time.
void LogMemoryAndDiscardTab(DiscardReason reason);
// Log memory statistics for the running processes, then call the callback.
void LogMemory(const std::string& title, const base::Closure& callback);
// Returns TabStats for all tabs in the current Chrome instance. The tabs are
// sorted first by most recently used to least recently used Browser and
// second by index in the Browser. |windows_sorted_by_z_index| is a list of
// Browser windows sorted by z-index, from topmost to bottommost. If left
// empty, no window occlusion checks will be performed. Must be called on the
// UI thread.
TabStatsList GetUnsortedTabStats(
const std::vector<gfx::NativeWindow>& windows_sorted_by_z_index =
std::vector<gfx::NativeWindow>()) const;
void AddObserver(TabLifecycleObserver* observer);
void RemoveObserver(TabLifecycleObserver* observer);
// Returns the auto-discardable state of the tab. When true, the tab is
// eligible to be automatically discarded when critical memory pressure hits,
// otherwise the tab is ignored and will never be automatically discarded.
// Note that this property doesn't block the discarding of the tab via other
// methods (about:discards for instance).
bool IsTabAutoDiscardable(content::WebContents* contents) const;
// Sets/clears the auto-discardable state of the tab.
void SetTabAutoDiscardableState(int32_t tab_id, bool state);
void SetTabAutoDiscardableState(content::WebContents* contents, bool state);
// Returns true when a given renderer can be purged if the specified
// renderer is eligible for purging.
bool CanPurgeBackgroundedRenderer(int render_process_id) const;
// Indicates how TabManager should load pending background tabs. The mode is
// recorded in tracing for easier debugging. The existing explicit numbering
// should be kept as is when new modes are added.
enum BackgroundTabLoadingMode {
kStaggered = 0, // Load a background tab after another tab is done loading.
kPaused = 1 // Pause loading background tabs unless a user selects it.
// Maybe throttle a tab's navigation based on current system status.
content::NavigationThrottle::ThrottleCheckResult MaybeThrottleNavigation(
BackgroundTabNavigationThrottle* throttle);
// Notifies TabManager that one navigation has finished (committed, aborted or
// replaced). TabManager should clean up the NavigationHandle objects bookkept
// before.
void OnDidFinishNavigation(content::NavigationHandle* navigation_handle);
// Called by TabManager::WebContentsData to notify TabManager that one tab is
// considered loaded. TabManager can decide which tab to load next.
void OnTabIsLoaded(content::WebContents* contents);
// Notifies TabManager that one tab WebContents has been destroyed. TabManager
// needs to clean up data related to that tab.
void OnWebContentsDestroyed(content::WebContents* contents);
// Returns true if |first| is considered less desirable to be killed than
// |second|.
static bool CompareTabStats(const TabStats& first, const TabStats& second);
// Returns the unique ID associated with a tab given the |web_contents|
// currently backing that tab.
static int32_t IdFromWebContents(content::WebContents* web_contents);
// Return whether tabs are being loaded during session restore.
bool IsSessionRestoreLoadingTabs() const {
return is_session_restore_loading_tabs_;
// Returns true if the tab was created by session restore and has not finished
// the first navigation.
bool IsTabInSessionRestore(content::WebContents* web_contents) const;
// Returns true if the tab was created by session restore and initially in
// foreground.
bool IsTabRestoredInForeground(content::WebContents* web_contents) const;
// Returns the number of background tabs that are loading in a background tab
// opening session.
size_t GetBackgroundTabLoadingCount() const;
// Returns the number of background tabs that are pending in a background tab
// opening session.
size_t GetBackgroundTabPendingCount() const;
// Returns the number of tabs open in all browser instances.
int GetTabCount() const;
// Returns the number of restored tabs during session restore. This is
// non-zero only during session restore.
int restored_tab_count() const;
// Duration during which a tab cannot be automatically discarded after having
// been active.
static constexpr base::TimeDelta kDiscardProtectionTime =
friend class TabManagerStatsCollectorTest;
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, PurgeBackgroundRenderer);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ActivateTabResetPurgeState);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ShouldPurgeAtDefaultTime);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, DefaultTimeToPurgeInCorrectRange);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, AutoDiscardable);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, CanOnlyDiscardOnce);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ChildProcessNotifications);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, Comparator);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, DiscardedTabKeepsLastActiveTime);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, DiscardWebContentsAt);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, InvalidOrEmptyURL);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, IsInternalPage);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, OomPressureListener);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ProtectPDFPages);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ProtectRecentlyUsedTabs);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ProtectVideoTabs);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, ReloadDiscardedTabContextMenu);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, TabManagerBasics);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, DiscardTabWithNonVisibleTabs);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, MaybeThrottleNavigation);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, OnDidFinishNavigation);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, OnTabIsLoaded);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, OnWebContentsDestroyed);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, OnDelayedTabSelected);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, TimeoutWhenLoadingBackgroundTabs);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, BackgroundTabLoadingMode);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, BackgroundTabLoadingSlots);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, BackgroundTabsLoadingOrdering);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, PauseAndResumeBackgroundTabOpening);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, IsInBackgroundTabOpeningSession);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, UrgentFastShutdownSingleTabProcess);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, UrgentFastShutdownSharedTabProcess);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, UrgentFastShutdownWithUnloadHandler);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, IsTabRestoredInForeground);
FRIEND_TEST_ALL_PREFIXES(TabManagerTest, EnablePageAlmostIdleSignal);
// The time of the first purging after a renderer is backgrounded.
// The initial value was chosen because most of users activate backgrounded
// tabs within 30 minutes. (c.f. Tabs.StateTransfer.Time_Inactive_Active)
static constexpr base::TimeDelta kDefaultMinTimeToPurge =
// The min/max time to purge ratio. The max time to purge is set to be
// min time to purge times this value.
const int kDefaultMinMaxTimeToPurgeRatio = 4;
// Finds TabStripModel which has a WebContents whose id is the given
// |tab_id|, and returns the WebContents index and the TabStripModel.
int FindTabStripModelById(int32_t tab_id, TabStripModel** model) const;
// Called by WebContentsData whenever the discard state of a WebContents
// changes, so that observers can be informed.
void OnDiscardedStateChange(content::WebContents* contents,
bool is_discarded);
// Called by WebContentsData whenever the auto-discardable state of a
// WebContents changes, so that observers can be informed.
void OnAutoDiscardableStateChange(content::WebContents* contents,
bool is_auto_discardable);
static void PurgeMemoryAndDiscardTab(DiscardReason reason);
// Returns true if the |url| represents an internal Chrome web UI page that
// can be easily reloaded and hence makes a good choice to discard.
static bool IsInternalPage(const GURL& url);
// Purges data structures in the browser that can be easily recomputed.
void PurgeBrowserMemory();
// Adds all the stats of the tabs in |browser_info| into |stats_list|.
// |window_is_active| indicates whether |browser_info|'s window is active.
// |window_is_visible| indicates whether |browser_info|'s window might be
// visible (true when window visibility is unknown).
void AddTabStats(const BrowserInfo& browser_info,
bool window_is_active,
bool window_is_visible,
TabStatsList* stats_list) const;
// Callback for when |update_timer_| fires. Takes care of executing the tasks
// that need to be run periodically (see comment in implementation).
void UpdateTimerCallback();
// Returns WebContents whose contents id matches the given |tab_id|.
content::WebContents* GetWebContentsById(int32_t tab_id) const;
// Returns a random time-to-purge whose min value is min_time_to_purge and max
// value is max_time_to_purge.
base::TimeDelta GetTimeToPurge(base::TimeDelta min_time_to_purge,
base::TimeDelta max_time_to_purge) const;
// Returns true if the tab specified by |content| is now eligible to have
// its memory purged.
bool ShouldPurgeNow(content::WebContents* content) const;
// Purges renderers in backgrounded tabs if the following conditions are
// satisfied:
// - the renderers are not purged yet,
// - the renderers are not playing media,
// (CanPurgeBackgroundedRenderer returns true)
// - the renderers are left inactive and background for time-to-purge.
// If renderers are purged, their internal states become 'purged'.
// The state is reset to be 'not purged' only when they are activated
// (=ActiveTabChanged is invoked).
void PurgeBackgroundedTabsIfNeeded();
// Does the actual discard by destroying the WebContents in |model| at |index|
// and replacing it by an empty one. Returns the new WebContents or NULL if
// the operation fails (return value used only in testing).
content::WebContents* DiscardWebContentsAt(int index,
TabStripModel* model,
DiscardReason reason);
// Pause or resume background tab opening according to memory pressure change
// if there are pending background tabs.
void PauseBackgroundTabOpeningIfNeeded();
void ResumeBackgroundTabOpeningIfNeeded();
// Called by the memory pressure listener when the memory pressure rises.
void OnMemoryPressure(
base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level);
// TabStripModelObserver overrides.
void TabChangedAt(content::WebContents* contents,
int index,
TabChangeType change_type) override;
void ActiveTabChanged(content::WebContents* old_contents,
content::WebContents* new_contents,
int index,
int reason) override;
void TabInsertedAt(TabStripModel* tab_strip_model,
content::WebContents* contents,
int index,
bool foreground) override;
// BrowserListObserver overrides.
void OnBrowserSetLastActive(Browser* browser) override;
// Returns true if the tab is currently playing audio or has played audio
// recently, or if the tab is currently accessing the camera, microphone or
// mirroring the display.
bool IsMediaTab(content::WebContents* contents) const;
// Returns the WebContentsData associated with |contents|. Also takes care of
// creating one if needed.
static WebContentsData* GetWebContentsData(content::WebContents* contents);
// Implementation of DiscardTab. Returns null if no tab was discarded.
// Otherwise returns the new web_contents of the discarded tab.
content::WebContents* DiscardTabImpl(DiscardReason reason);
// Returns true if |web_contents| is the active WebContents in the last active
// Browser.
bool IsActiveWebContentsInActiveBrowser(content::WebContents* contents) const;
// Returns a list of BrowserInfo constructed from either
// |test_browser_info_list_| or BrowserList. The first BrowserInfo in the list
// corresponds to the last active Browser.
std::vector<BrowserInfo> GetBrowserInfoList() const;
void OnSessionRestoreStartedLoadingTabs();
void OnSessionRestoreFinishedLoadingTabs();
void OnWillRestoreTab(content::WebContents* contents);
// Returns true if it is in BackgroundTabOpening session, which is defined as
// the duration from the time when the browser starts to load background tabs
// until the time when browser has finished loading those tabs. During the
// session, the session can end when background tabs' loading are paused due
// to memory pressure. A new session starts when background tabs' loading
// resume when memory pressure returns to normal.
bool IsInBackgroundTabOpeningSession() const;
// Returns true if TabManager can start loading next tab.
bool CanLoadNextTab() const;
// Start |force_load_timer_| to load the next background tab if the timer
// expires before the current tab loading is finished.
void StartForceLoadTimer();
// Start loading the next background tab if needed. This is called when:
// 1. a tab has finished loading;
// 2. or a tab has been destroyed;
// 3. or memory pressure is relieved;
// 4. or |force_load_timer_| fires.
void LoadNextBackgroundTabIfNeeded();
// Resume the tab's navigation if it is pending right now. This is called when
// a tab is selected.
void ResumeTabNavigationIfNeeded(content::WebContents* contents);
// Resume navigation.
void ResumeNavigation(BackgroundTabNavigationThrottle* throttle);
// Remove the pending navigation for the provided WebContents. Return the
// removed NavigationThrottle. Return nullptr if it doesn't exists.
BackgroundTabNavigationThrottle* RemovePendingNavigationIfNeeded(
content::WebContents* contents);
// Returns true if |first| is considered to resume navigation before |second|.
static bool ComparePendingNavigations(
const BackgroundTabNavigationThrottle* first,
const BackgroundTabNavigationThrottle* second);
// Returns the number of tabs that are not pending load or discarded.
int GetNumAliveTabs() const;
// Check if the tab is loading. Use only in tests.
bool IsTabLoadingForTest(content::WebContents* contents) const;
// Check if the navigation is delayed. Use only in tests.
bool IsNavigationDelayedForTest(
const content::NavigationHandle* navigation_handle) const;
// Set |loading_slots_|. Use only in tests.
void SetLoadingSlotsForTest(size_t loading_slots) {
loading_slots_ = loading_slots;
// Reset |memory_pressure_listener_| in test so that the test is not affected
// by memory pressure.
void ResetMemoryPressureListenerForTest() {
TabManagerStatsCollector* stats_collector() { return stats_collector_.get(); }
// Timer to periodically update the stats of the renderers.
base::RepeatingTimer update_timer_;
// A listener to global memory pressure events.
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
// Number of times a tab has been discarded, for statistics.
int discard_count_;
// A backgrounded renderer will be purged between min_time_to_purge_ and
// max_time_to_purge_.
base::TimeDelta min_time_to_purge_;
base::TimeDelta max_time_to_purge_;
#if defined(OS_CHROMEOS)
std::unique_ptr<TabManagerDelegate> delegate_;
// Responsible for automatically registering this class as an observer of all
// TabStripModels. Automatically tracks browsers as they come and go.
BrowserTabStripTracker browser_tab_strip_tracker_;
// Injected BrowserInfo list. Allows this to be tested end-to-end without
// requiring a full browser environment. If specified these BrowserInfo will
// be crawled as the authoritative source of tabs, otherwise the BrowserList
// and associated Browser objects are crawled. The first BrowserInfo in the
// list corresponds to the last active Browser.
// TODO(chrisha): Factor out tab-strip model enumeration to a helper class,
// and make a delegate that centralizes all testing seams.
std::vector<BrowserInfo> test_browser_info_list_;
// List of observers that will receive notifications on state changes.
base::ObserverList<TabLifecycleObserver> observers_;
bool is_session_restore_loading_tabs_;
size_t restored_tab_count_;
class TabManagerSessionRestoreObserver;
std::unique_ptr<TabManagerSessionRestoreObserver> session_restore_observer_;
// The mode that TabManager is using to load pending background tabs.
BackgroundTabLoadingMode background_tab_loading_mode_;
// When the timer fires, it forces loading the next background tab if needed.
std::unique_ptr<base::OneShotTimer> force_load_timer_;
// The list of navigations that are delayed.
std::vector<BackgroundTabNavigationThrottle*> pending_navigations_;
// The tabs that are currently loading. We will consider loading the next
// background tab when these tabs have finished loading or a background tab
// is brought to foreground.
std::set<content::WebContents*> loading_contents_;
// The number of loading slots that TabManager can use to load background tabs
// in parallel.
size_t loading_slots_;
// |resource_coordinator_signal_observer_| is owned by TabManager and is used
// to receive various signals from ResourceCoordinator.
// Records UMAs for tab and system-related events and properties during
// session restore.
std::unique_ptr<TabManagerStatsCollector> stats_collector_;
// Weak pointer factory used for posting delayed tasks.
base::WeakPtrFactory<TabManager> weak_ptr_factory_;
} // namespace resource_coordinator