Commit 5a088038 authored by rdevlin.cronin's avatar rdevlin.cronin Committed by Commit bot

[Extensions Page] Fix the flicker of "No extensions" before extension data loads

There was a flicker caused because we remove the 'loading'
class from the page before the extensions list finishes
updating itself (asynchronously).

BUG=469053
BUG=469332

Review URL: https://codereview.chromium.org/1025703003

Cr-Commit-Position: refs/heads/master@{#321869}
parent 717f2c07
...@@ -140,11 +140,16 @@ cr.define('extensions', function() { ...@@ -140,11 +140,16 @@ cr.define('extensions', function() {
/** /**
* Creates a new list of extensions. * Creates a new list of extensions.
* @param {Object=} opt_propertyBag Optional properties.
* @constructor * @constructor
* @extends {HTMLDivElement} * @extends {HTMLDivElement}
*/ */
var ExtensionList = cr.ui.define('div'); function ExtensionList() {
var div = document.createElement('div');
div.__proto__ = ExtensionList.prototype;
/** @private {!Array<ExtensionInfo>} */
div.extensions_ = [];
return div;
}
/** /**
* @type {Object<string, number>} A map from extension id to last reloaded * @type {Object<string, number>} A map from extension id to last reloaded
...@@ -188,29 +193,61 @@ cr.define('extensions', function() { ...@@ -188,29 +193,61 @@ cr.define('extensions', function() {
*/ */
butterbarShown_: false, butterbarShown_: false,
decorate: function() { /**
chrome.developerPrivate.getExtensionsInfo( * Whether or not incognito mode is available.
{includeDisabled: true, includeTerminated: true}, * @private {boolean}
function(extensions) { */
// Sort in order of unpacked vs. packed, followed by name, followed by incognitoAvailable_: false,
// id.
extensions.sort(function(a, b) { /**
function compare(x, y) { * Whether or not the app info dialog is enabled.
return x < y ? -1 : (x > y ? 1 : 0); * @private {boolean}
} */
function compareLocation(x, y) { enableAppInfoDialog_: false,
return x.location == chrome.developerPrivate.Location.UNPACKED ?
-1 : (x.location == y.location ? 0 : 1); /**
} * Updates the extensions on the page.
return compareLocation(a, b) || * @param {boolean} incognitoAvailable Whether or not incognito is allowed.
compare(a.name.toLowerCase(), b.name.toLowerCase()) || * @param {boolean} enableAppInfoDialog Whether or not the app info dialog
compare(a.id, b.id); * is enabled.
}); * @return {Promise} A promise that is resolved once the extensions data is
this.extensions_ = extensions; * fully updated.
this.showExtensionNodes_(); */
updateExtensionsData: function(incognitoAvailable, enableAppInfoDialog) {
// If we start to need more information about the extension configuration,
// consider passing in the full object from the ExtensionSettings.
this.incognitoAvailable_ = incognitoAvailable;
this.enableAppInfoDialog_ = enableAppInfoDialog;
return new Promise(function(resolve, reject) {
chrome.developerPrivate.getExtensionsInfo(
{includeDisabled: true, includeTerminated: true},
function(extensions) {
// Sort in order of unpacked vs. packed, followed by name, followed by
// id.
extensions.sort(function(a, b) {
function compare(x, y) {
return x < y ? -1 : (x > y ? 1 : 0);
}
function compareLocation(x, y) {
return x.location == chrome.developerPrivate.Location.UNPACKED ?
-1 : (x.location == y.location ? 0 : 1);
}
return compareLocation(a, b) ||
compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
compare(a.id, b.id);
});
this.extensions_ = extensions;
this.showExtensionNodes_();
resolve();
}.bind(this));
}.bind(this)); }.bind(this));
}, },
/** @return {number} The number of extensions being displayed. */
getNumExtensions: function() {
return this.extensions_.length;
},
getIdQueryParam_: function() { getIdQueryParam_: function() {
return parseQueryParams(document.location)['id']; return parseQueryParams(document.location)['id'];
}, },
...@@ -268,9 +305,6 @@ cr.define('extensions', function() { ...@@ -268,9 +305,6 @@ cr.define('extensions', function() {
var idToOpenOptions = this.getOptionsQueryParam_(); var idToOpenOptions = this.getOptionsQueryParam_();
if (idToOpenOptions && $(idToOpenOptions)) if (idToOpenOptions && $(idToOpenOptions))
this.showEmbeddedExtensionOptions_(idToOpenOptions, true); this.showEmbeddedExtensionOptions_(idToOpenOptions, true);
var noExtensions = this.extensions_.length == 0;
this.classList.toggle('empty-extension-list', noExtensions);
}, },
/** Updates each row's focusable elements without rebuilding the grid. */ /** Updates each row's focusable elements without rebuilding the grid. */
...@@ -545,7 +579,7 @@ cr.define('extensions', function() { ...@@ -545,7 +579,7 @@ cr.define('extensions', function() {
// The 'allow in incognito' checkbox. // The 'allow in incognito' checkbox.
this.updateVisibility_(row, '.incognito-control', this.updateVisibility_(row, '.incognito-control',
isActive && this.data_.incognitoAvailable, isActive && this.incognitoAvailable_,
function(item) { function(item) {
var incognito = item.querySelector('input'); var incognito = item.querySelector('input');
incognito.disabled = !extension.incognitoAccess.isEnabled; incognito.disabled = !extension.incognitoAccess.isEnabled;
...@@ -594,7 +628,7 @@ cr.define('extensions', function() { ...@@ -594,7 +628,7 @@ cr.define('extensions', function() {
// The 'View in Web Store/View Web Site' link. // The 'View in Web Store/View Web Site' link.
var siteLinkEnabled = !!extension.homepageUrl && var siteLinkEnabled = !!extension.homepageUrl &&
!this.data_.enableAppInfoDialog; !this.enableAppInfoDialog_;
this.updateVisibility_(row, '.site-link', siteLinkEnabled, this.updateVisibility_(row, '.site-link', siteLinkEnabled,
function(item) { function(item) {
item.href = extension.homepageUrl; item.href = extension.homepageUrl;
...@@ -721,7 +755,7 @@ cr.define('extensions', function() { ...@@ -721,7 +755,7 @@ cr.define('extensions', function() {
depNode.querySelector('.dep-extension-id').textContent = depNode.querySelector('.dep-extension-id').textContent =
dependentExtension.id; dependentExtension.id;
dependentList.appendChild(depNode); dependentList.appendChild(depNode);
}); }, this);
}); });
// The active views. // The active views.
......
...@@ -141,6 +141,10 @@ html:not(.focus-outline-visible) ...@@ -141,6 +141,10 @@ html:not(.focus-outline-visible)
font-weight: bold; font-weight: bold;
} }
#no-extensions {
margin-top: 3em;
}
#suggest-gallery { #suggest-gallery {
-webkit-padding-start: 10px; -webkit-padding-start: 10px;
} }
...@@ -160,17 +164,6 @@ html[dir=rtl] #footer-section { ...@@ -160,17 +164,6 @@ html[dir=rtl] #footer-section {
line-height: 32px; line-height: 32px;
} }
.empty-extension-list {
height: 3em;
}
.loading #no-extensions,
.loading #footer-section,
#extension-settings-list:not(.empty-extension-list) ~ #no-extensions,
.empty-extension-list ~ #footer-section {
display: none;
}
.extension-list-item-wrapper { .extension-list-item-wrapper {
margin: 23px 0; margin: 23px 0;
} }
......
<!doctype html> <!doctype html>
<html i18n-values="dir:textdirection;lang:language" class="loading"> <html i18n-values="dir:textdirection;lang:language">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
...@@ -114,21 +114,22 @@ ...@@ -114,21 +114,22 @@
</div> </div>
</div> </div>
<include src="extension_load_error.html"> <include src="extension_load_error.html">
<div id="extension-settings-list" class="empty-extension-list"></div> <div id="extension-list-wrapper" hidden>
<div id="no-extensions"> <div id="footer-section">
<a target="_blank" class="more-extensions-link"
i18n-values="href:extensionSettingsGetMoreExtensionsUrl"
i18n-content="extensionSettingsGetMoreExtensions"></a>
<a is="action-link" class="extension-commands-config"
i18n-content="extensionSettingsCommandsLink" hidden></a>
</div>
</div>
<div id="no-extensions" hidden>
<span id="no-extensions-message" <span id="no-extensions-message"
i18n-content="extensionSettingsNoExtensions"></span> i18n-content="extensionSettingsNoExtensions"></span>
<span id="suggest-gallery" class="more-extensions-link" <span id="suggest-gallery" class="more-extensions-link"
i18n-values=".innerHTML:extensionSettingsSuggestGallery"> i18n-values=".innerHTML:extensionSettingsSuggestGallery">
</span> </span>
</div> </div>
<div id="footer-section">
<a target="_blank" class="more-extensions-link"
i18n-values="href:extensionSettingsGetMoreExtensionsUrl"
i18n-content="extensionSettingsGetMoreExtensions"></a>
<a is="action-link" class="extension-commands-config"
i18n-content="extensionSettingsCommandsLink" hidden></a>
</div>
</div> </div>
<span id="font-measuring-div"></span> <span id="font-measuring-div"></span>
......
...@@ -133,6 +133,11 @@ cr.define('extensions', function() { ...@@ -133,6 +133,11 @@ cr.define('extensions', function() {
// Set the title. // Set the title.
uber.setTitle(loadTimeData.getString('extensionSettings')); uber.setTitle(loadTimeData.getString('extensionSettings'));
var extensionList = new ExtensionList();
extensionList.id = 'extension-settings-list';
var wrapper = $('extension-list-wrapper');
wrapper.insertBefore(extensionList, wrapper.firstChild);
// This will request the data to show on the page and will get a response // This will request the data to show on the page and will get a response
// back in returnExtensionsData. // back in returnExtensionsData.
chrome.send('extensionSettingsRequestExtensionsData'); chrome.send('extensionSettingsRequestExtensionsData');
...@@ -141,7 +146,7 @@ cr.define('extensions', function() { ...@@ -141,7 +146,7 @@ cr.define('extensions', function() {
$('toggle-dev-on').addEventListener('change', function(e) { $('toggle-dev-on').addEventListener('change', function(e) {
this.updateDevControlsVisibility_(true); this.updateDevControlsVisibility_(true);
$('extension-settings-list').updateFocusableElements(); extensionList.updateFocusableElements();
chrome.send('extensionSettingsToggleDeveloperMode', chrome.send('extensionSettingsToggleDeveloperMode',
[$('toggle-dev-on').checked]); [$('toggle-dev-on').checked]);
}.bind(this)); }.bind(this));
...@@ -298,15 +303,6 @@ cr.define('extensions', function() { ...@@ -298,15 +303,6 @@ cr.define('extensions', function() {
* @param {ExtensionDataResponse} extensionsData * @param {ExtensionDataResponse} extensionsData
*/ */
ExtensionSettings.returnExtensionsData = function(extensionsData) { ExtensionSettings.returnExtensionsData = function(extensionsData) {
// We can get called many times in short order, thus we need to
// be careful to remove the 'finished loading' timeout.
if (this.loadingTimeout_)
window.clearTimeout(this.loadingTimeout_);
document.documentElement.classList.add('loading');
this.loadingTimeout_ = window.setTimeout(function() {
document.documentElement.classList.remove('loading');
}, 0);
webuiResponded = true; webuiResponded = true;
var supervised = extensionsData.profileIsSupervised; var supervised = extensionsData.profileIsSupervised;
...@@ -324,9 +320,23 @@ cr.define('extensions', function() { ...@@ -324,9 +320,23 @@ cr.define('extensions', function() {
instance.updateDevControlsVisibility_(false); instance.updateDevControlsVisibility_(false);
$('load-unpacked').disabled = extensionsData.loadUnpackedDisabled; $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
var extensionList = $('extension-settings-list');
ExtensionList.prototype.data_ = extensionsData; extensionList.updateExtensionsData(
ExtensionList.decorate($('extension-settings-list')); extensionsData.incognitoAvailable,
extensionsData.enableAppInfoDialog).then(function() {
// We can get called many times in short order, thus we need to
// be careful to remove the 'finished loading' timeout.
if (this.loadingTimeout_)
window.clearTimeout(this.loadingTimeout_);
document.documentElement.classList.add('loading');
this.loadingTimeout_ = window.setTimeout(function() {
document.documentElement.classList.remove('loading');
}, 0);
var hasExtensions = extensionList.getNumExtensions() != 0;
$('no-extensions').hidden = hasExtensions;
$('extension-list-wrapper').hidden = !hasExtensions;
}.bind(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