| // 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. |
| |
| cr.define('discards_tab', function() { |
| 'use strict'; |
| |
| /** |
| * @param {mojom.LifecycleUnitState} state The discard state. |
| * @return {boolean} Whether the state is related to discarding. |
| */ |
| function isDiscardRelatedState(state) { |
| return state == mojom.LifecycleUnitState.PENDING_DISCARD || |
| state == mojom.LifecycleUnitState.DISCARDED; |
| } |
| |
| /** |
| * Compares two TabDiscardsInfos based on the data in the provided sort-key. |
| * @param {string} sortKey The key of the sort. See the "data-sort-key" |
| * attribute of the table headers for valid sort-keys. |
| * @param {boolean|number|string} a The first value being compared. |
| * @param {boolean|number|string} b The second value being compared. |
| * @return {number} A negative number if a < b, 0 if a == b, and a positive |
| * number if a > b. |
| */ |
| function compareTabDiscardsInfos(sortKey, a, b) { |
| let val1 = a[sortKey]; |
| let val2 = b[sortKey]; |
| |
| // Compares strings. |
| if (sortKey == 'title' || sortKey == 'tabUrl') { |
| val1 = val1.toLowerCase(); |
| val2 = val2.toLowerCase(); |
| if (val1 == val2) { |
| return 0; |
| } |
| return val1 > val2 ? 1 : -1; |
| } |
| |
| // Compares boolean fields. |
| if (['canFreeze', 'canDiscard', 'isAutoDiscardable'].includes(sortKey)) { |
| if (val1 == val2) { |
| return 0; |
| } |
| return val1 ? 1 : -1; |
| } |
| |
| // Compare lifecycle state. This is actually a compound key. |
| if (sortKey == 'state') { |
| // If the keys are discarding state, then break ties using the discard |
| // reason. |
| if (val1 == val2 && isDiscardRelatedState(val1)) { |
| val1 = a['discardReason']; |
| val2 = b['discardReason']; |
| } |
| return val1 - val2; |
| } |
| |
| // Compares numeric fields. |
| // NOTE: visibility, loadingState and state are represented as a numeric |
| // value. |
| if ([ |
| 'visibility', |
| 'loadingState', |
| 'discardCount', |
| 'utilityRank', |
| 'reactivationScore', |
| 'lastActiveSeconds', |
| 'siteEngagementScore', |
| ].includes(sortKey)) { |
| return val1 - val2; |
| } |
| |
| assertNotReached('Unsupported sort key: ' + sortKey); |
| return 0; |
| } |
| |
| return { |
| compareTabDiscardsInfos: compareTabDiscardsInfos, |
| }; |
| }); |
| |
| |
| Polymer({ |
| is: 'discards-tab', |
| |
| behaviors: [SortedTableBehavior], |
| |
| properties: { |
| /** |
| * List of tabinfos. |
| * @private {?Array<!mojom.TabDiscardsInfo>} |
| */ |
| tabInfos_: { |
| type: Array, |
| }, |
| }, |
| |
| /** @private The current update timer if any. */ |
| updateTimer_: 0, |
| |
| /** @private {(mojom.DiscardsDetailsProviderPtr|null)} */ |
| uiHandler_: null, |
| |
| /** @override */ |
| ready: function() { |
| this.setSortKey('utilityRank'); |
| this.uiHandler_ = discards.getOrCreateUiHandler(); |
| |
| this.updateTable_(); |
| }, |
| |
| /** |
| * Returns a sort function to compare tab infos based on the provided sort key |
| * and a boolean reverse flag. |
| * @param {string} sortKey The sort key for the returned function. |
| * @param {boolean} sortReverse True if sorting is reversed. |
| * @return {function({Object}, {Object}): number} |
| * A comparison function that compares two tab infos, returns |
| * negative number if a < b, 0 if a == b, and a positive |
| * number if a > b. |
| * @private |
| */ |
| computeSortFunction_: function(sortKey, sortReverse) { |
| // Polymer 2.0 may invoke multi-property observers before all properties |
| // are defined. |
| if (!sortKey) { |
| return (a, b) => 0; |
| } |
| |
| return function(a, b) { |
| const comp = discards_tab.compareTabDiscardsInfos(sortKey, a, b); |
| return sortReverse ? -comp : comp; |
| }; |
| }, |
| |
| /** |
| * Returns a string representation of a visibility enum value for display in |
| * a table. |
| * @param {mojom.LifecycleUnitVisibility} visibility A visibility value. |
| * @return {string} A string representation of the visibility. |
| * @private |
| */ |
| visibilityToString_: function(visibility) { |
| switch (visibility) { |
| case mojom.LifecycleUnitVisibility.HIDDEN: |
| return 'hidden'; |
| case mojom.LifecycleUnitVisibility.OCCLUDED: |
| return 'occluded'; |
| case mojom.LifecycleUnitVisibility.VISIBLE: |
| return 'visible'; |
| } |
| assertNotReached('Unknown visibility: ' + visibility); |
| }, |
| |
| /** |
| * Returns a string representation of a loading state enum value for display |
| * in a table. |
| * @param {mojom.LifecycleUnitLoadingState} loadingState A loading state |
| * value. |
| * @return {string} A string representation of the loading state. |
| * @private |
| */ |
| loadingStateToString_: function(loadingState) { |
| switch (loadingState) { |
| case mojom.LifecycleUnitLoadingState.UNLOADED: |
| return 'unloaded'; |
| case mojom.LifecycleUnitLoadingState.LOADING: |
| return 'loading'; |
| case mojom.LifecycleUnitLoadingState.LOADED: |
| return 'loaded'; |
| } |
| assertNotReached('Unknown loadingState: ' + loadingState); |
| }, |
| |
| /** |
| * Returns a string representation of a discard reason. |
| * @param {mojom.LifecycleUnitDiscardReason} reason The discard reason. |
| * @return {string} A string representation of the discarding reason. |
| * @private |
| */ |
| discardReasonToString_: function(reason) { |
| switch (reason) { |
| case mojom.LifecycleUnitDiscardReason.EXTERNAL: |
| return 'external'; |
| case mojom.LifecycleUnitDiscardReason.PROACTIVE: |
| return 'proactive'; |
| case mojom.LifecycleUnitDiscardReason.URGENT: |
| return 'urgent'; |
| } |
| assertNotReached('Unknown discard reason: ' + reason); |
| }, |
| |
| /** |
| * Returns a string representation of a lifecycle state. |
| * @param {mojom.LifecycleUnitState} state The lifecycle state. |
| * @param {mojom.LifecycleUnitDiscardReason} reason The discard reason. This |
| * is only used if the state is discard related. |
| * @param {mojom.LifecycleUnitVisibility} visibility A visibility value. |
| * @param {boolean} hasFocus Whether or not the tab has input focus. |
| * @return {string} A string representation of the lifecycle state, augmented |
| * with the discard reason if appropriate. |
| * @private |
| */ |
| lifecycleStateToString_: function(state, reason, visibility, hasFocus) { |
| const pageLifecycleStateFromVisibilityAndFocus = function() { |
| switch (visibility) { |
| case mojom.LifecycleUnitVisibility.HIDDEN: |
| case mojom.LifecycleUnitVisibility.OCCLUDED: |
| // An occluded page is also considered hidden. |
| return 'hidden'; |
| case mojom.LifecycleUnitVisibility.VISIBLE: |
| return hasFocus ? 'active' : 'passive'; |
| } |
| assertNotReached('Unknown visibility: ' + visibility); |
| }; |
| |
| switch (state) { |
| case mojom.LifecycleUnitState.ACTIVE: |
| return pageLifecycleStateFromVisibilityAndFocus(); |
| case mojom.LifecycleUnitState.THROTTLED: |
| return pageLifecycleStateFromVisibilityAndFocus() + ' (throttled)'; |
| case mojom.LifecycleUnitState.PENDING_FREEZE: |
| return pageLifecycleStateFromVisibilityAndFocus() + ' (pending frozen)'; |
| case mojom.LifecycleUnitState.FROZEN: |
| return 'frozen'; |
| case mojom.LifecycleUnitState.PENDING_DISCARD: |
| return pageLifecycleStateFromVisibilityAndFocus() + |
| ' (pending discard (' + this.discardReasonToString_(reason) + '))'; |
| case mojom.LifecycleUnitState.DISCARDED: |
| return 'discarded (' + this.discardReasonToString_(reason) + ')'; |
| case mojom.LifecycleUnitState.PENDING_UNFREEZE: |
| return 'frozen (pending unfreeze)'; |
| } |
| assertNotReached('Unknown lifecycle state: ' + state); |
| }, |
| |
| /** |
| * Dispatches a request to update tabInfos_. |
| * @private |
| */ |
| updateTableImpl_: function() { |
| this.uiHandler_.getTabDiscardsInfo().then(response => { |
| this.tabInfos_ = response.infos; |
| }); |
| }, |
| |
| /** |
| * A wrapper to updateTableImpl_ that is called due to user action and not |
| * due to the automatic timer. Cancels the existing timer and reschedules it |
| * after rendering instantaneously. |
| * @private |
| */ |
| updateTable_: function() { |
| if (this.updateTimer_) { |
| clearInterval(this.updateTimer_); |
| } |
| this.updateTableImpl_(); |
| this.updateTimer_ = setInterval(this.updateTableImpl_.bind(this), 1000); |
| }, |
| |
| /** |
| * Formats an items reactivation for display. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {string} The formatted reactivation score. |
| * @private |
| */ |
| getReactivationScore_: function(item) { |
| return item.hasReactivationScore ? item.reactivationScore.toFixed(4) : |
| 'N/A'; |
| }, |
| |
| /** |
| * Formats an items site engagement score for display. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {string} The formatted site engagemetn score. |
| * @private |
| */ |
| getSiteEngagementScore_: function(item) { |
| return item.siteEngagementScore.toFixed(1); |
| }, |
| |
| /** |
| * Retrieves favicon style tag value for an item. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {string} A style to retrieve and display the item's favicon. |
| * @private |
| */ |
| getFavIconStyle_: function(item) { |
| return 'background-image:' + cr.icon.getFavicon(item.tabUrl); |
| }, |
| |
| /** |
| * Formats an items lifecycle state for display. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {string} A human readable lifecycle state. |
| * @private |
| */ |
| getLifeCycleState_: function(item) { |
| if (item.loadingState != mojom.LifecycleUnitLoadingState.UNLOADED || |
| item.discardCount > 0) { |
| return this.lifecycleStateToString_( |
| item.state, item.discardReason, item.visibility, item.hasFocus); |
| } else { |
| return ''; |
| } |
| }, |
| |
| /** |
| * Returns a string representation of a boolean value for display in a table. |
| * @param {boolean} value A boolean value. |
| * @return {string} A string representing the bool. |
| * @private |
| */ |
| boolToString_: function(value) { |
| return discards.boolToString(value); |
| }, |
| |
| /** |
| * Converts a |secondsAgo| duration to a user friendly string. |
| * @param {number} secondsAgo The duration to render. |
| * @return {string} An English string representing the duration. |
| * @private |
| */ |
| durationToString_: function(secondsAgo) { |
| return discards.durationToString(secondsAgo); |
| }, |
| |
| /** |
| * Tests whether an item has reasons why it cannot be frozen. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {boolean} true iff there are reasons why the item cannot be frozen. |
| * @private |
| */ |
| hasCannotFreezeReasons_: function(item) { |
| return item.cannotFreezeReasons.length != 0; |
| }, |
| /** |
| * Tests whether an item has reasons why it cannot be discarded. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {boolean} true iff there are reasons why the item cannot be |
| * discarded. |
| * @private |
| */ |
| hasCannotDiscardReasons_: function(item) { |
| return item.cannotDiscardReasons.length != 0; |
| }, |
| |
| /** |
| * Tests whether an item can be loaded. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {boolean} true iff the item can be loaded. |
| * @private |
| */ |
| canLoad_: function(item) { |
| return item.loadingState == mojom.LifecycleUnitLoadingState.UNLOADED; |
| }, |
| |
| /** |
| * Tests whether an item can be frozen. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {boolean} true iff the item can be frozen. |
| * @private |
| */ |
| canFreeze_: function(item) { |
| if (item.visibility == mojom.LifecycleUnitVisibility.HIDDEN || |
| item.visibility == mojom.LifecycleUnitVisibility.OCCLUDED) { |
| // Only tabs that aren't visible can be frozen for now. |
| switch (item.state) { |
| case mojom.LifecycleUnitState.DISCARDED: |
| case mojom.LifecycleUnitState.PENDING_DISCARD: |
| case mojom.LifecycleUnitState.FROZEN: |
| case mojom.LifecycleUnitState.PENDING_FREEZE: |
| return false; |
| } |
| return true; |
| } |
| return false; |
| }, |
| |
| /** |
| * Tests whether an item can be discarded. |
| * @param {mojom.TabDiscardsInfo} item The item in question. |
| * @return {boolean} true iff the item can be discarded. |
| * @private |
| */ |
| canDiscard_: function(item) { |
| if (item.visibility == mojom.LifecycleUnitVisibility.HIDDEN || |
| item.visibility == mojom.LifecycleUnitVisibility.OCCLUDED) { |
| // Only tabs that aren't visible can be discarded for now. |
| switch (item.state) { |
| case mojom.LifecycleUnitState.DISCARDED: |
| case mojom.LifecycleUnitState.PENDING_DISCARD: |
| return false; |
| } |
| return true; |
| } |
| return false; |
| }, |
| |
| /** |
| * Event handler that toggles the auto discardable flag on an item. |
| * @param {Event} e The event. |
| * @private |
| */ |
| toggleAutoDiscardable_: function(e) { |
| const item = e.model.item; |
| this.uiHandler_.setAutoDiscardable(item.id, !item.isAutoDiscardable) |
| .then(this.updateTable_.bind(this)); |
| }, |
| |
| /** |
| * Event handler that loads a tab. |
| * @param {Event} e The event. |
| * @private |
| */ |
| loadTab_: function(e) { |
| this.uiHandler_.loadById(e.model.item.id); |
| }, |
| |
| /** |
| * Event handler that freezes a tab. |
| * @param {Event} e The event. |
| * @private |
| */ |
| freezeTab_: function(e) { |
| this.uiHandler_.freezeById(e.model.item.id); |
| }, |
| |
| /** |
| * Implementation function for tab discarding. |
| * @param {Event} e The event. |
| * @param {boolean} urgent True if tab should be urgently discarded. |
| * @private |
| */ |
| discardTabImpl_: function(e, urgent) { |
| this.uiHandler_.discardById(e.model.item.id, urgent) |
| .then(this.updateTable_.bind(this)); |
| }, |
| |
| /** |
| * Event handler that discards a given tab. |
| * @param {Event} e The event. |
| * @private |
| */ |
| discardTab_: function(e) { |
| this.discardTabImpl_(e, false); |
| }, |
| |
| /** |
| * Event handler that discards a given tab urgently. |
| * @param {Event} e The event. |
| * @private |
| */ |
| urgentDiscardTab_: function(e) { |
| this.discardTabImpl_(e, true); |
| }, |
| |
| /** |
| * Implementation function to discard the next discardable tab. |
| * @param {boolean} urgent True if tab should be urgently discarded. |
| * @private |
| */ |
| discardImpl_: function(urgent) { |
| this.uiHandler_.discard(urgent).then(() => { |
| this.updateTable_(); |
| }); |
| }, |
| |
| /** |
| * Event handler that discards the next discardable tab. |
| * @param {Event} e The event. |
| * @private |
| */ |
| discardNow_: function(e) { |
| this.discardImpl_(false); |
| }, |
| |
| /** |
| * Event handler that discards the next discardable tab urgently. |
| * @param {Event} e The event. |
| * @private |
| */ |
| discardUrgentNow_: function(e) { |
| this.discardImpl_(true); |
| }, |
| }); |