Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(596)

Unified Diff: chrome/browser/resources/extensions/extension_list.js

Issue 893453002: Stop rebuilding all elements in chrome://extensions to preserve focus on refresh (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: DNC - clean up enabled checkbox code Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/extensions/extension_list.js
diff --git a/chrome/browser/resources/extensions/extension_list.js b/chrome/browser/resources/extensions/extension_list.js
index 11bb9fe5e2ee60997f5031373a3dd6a71f02b37e..e53bdf1ab3d0dadebede4c27950e64b972ca30f2 100644
--- a/chrome/browser/resources/extensions/extension_list.js
+++ b/chrome/browser/resources/extensions/extension_list.js
@@ -103,9 +103,13 @@ cr.define('options', function() {
*/
optionsShown_: false,
- decorate: function() {
- this.textContent = '';
+ /**
+ * Necessary to only show the butterbar once.
+ * @private {boolean}
+ */
+ butterbarShown_: false,
+ decorate: function() {
this.showExtensionNodes_();
},
@@ -123,7 +127,14 @@ cr.define('options', function() {
*/
showExtensionNodes_: function() {
// Iterate over the extension data and add each item to the list.
- this.data_.extensions.forEach(this.createNode_, this);
+ this.data_.extensions.forEach(function(extension) {
+ var node = $(extension.id);
+
+ if (node)
+ this.updateNode_(extension, node);
+ else
+ this.createNode_(extension);
+ }, this);
var idToHighlight = this.getIdQueryParam_();
if (idToHighlight && $(idToHighlight))
@@ -155,393 +166,63 @@ cr.define('options', function() {
setScrollTopForDocument(document, scrollTop);
},
+ // TODO(hcarmona): Remove this temporary hack before clicking commit.
+ <include src="create_node.js">
+ <include src="update_node.js">
+
/**
- * Synthesizes and initializes an HTML element for the extension metadata
- * given in |extension|.
- * @param {ExtensionData} extension A dictionary of extension metadata.
+ * Adds a listener to an element.
+ * @param {string} type The type of listener to add.
+ * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {string} query A query to select an element in |node|.
+ * @param {function({Event} e)} onclick The function that should be called
+ * on click.
* @private
*/
- createNode_: function(extension) {
- var template = $('template-collection').querySelector(
- '.extension-list-item-wrapper');
- var node = template.cloneNode(true);
- node.id = extension.id;
-
- if (!extension.enabled || extension.terminated)
- node.classList.add('inactive-extension');
-
- var classes = [];
- if (extension.managedInstall) {
- classes.push('policy-controlled', 'may-not-modify');
- } else if (extension.dependentExtensions.length > 0) {
- classes.push('may-not-remove', 'may-not-modify');
- } else if (extension.recommendedInstall) {
- classes.push('may-not-remove');
- } else if (extension.suspiciousInstall ||
- extension.corruptInstall ||
- extension.updateRequiredByPolicy) {
- classes.push('may-not-modify');
- }
- node.classList.add.apply(node.classList, classes);
-
- var idToHighlight = this.getIdQueryParam_();
- if (node.id == idToHighlight)
- node.classList.add('extension-highlight');
-
- var item = node.querySelector('.extension-list-item');
- // Prevent the image cache of extension icon by using the reloaded
- // timestamp as a query string. The timestamp is recorded when the user
- // clicks the 'Reload' link. http://crbug.com/159302.
- if (extensionReloadedTimestamp[extension.id]) {
- item.style.backgroundImage =
- 'url(' + extension.icon + '?' +
- extensionReloadedTimestamp[extension.id] + ')';
- } else {
- item.style.backgroundImage = 'url(' + extension.icon + ')';
- }
-
- var title = node.querySelector('.extension-title');
- title.textContent = extension.name;
-
- var version = node.querySelector('.extension-version');
- version.textContent = extension.version;
-
- var locationText = node.querySelector('.location-text');
- locationText.textContent = extension.locationText;
-
- var blacklistText = node.querySelector('.blacklist-text');
- blacklistText.textContent = extension.blacklistText;
-
- var description = document.createElement('span');
- description.textContent = extension.description;
- node.querySelector('.extension-description').appendChild(description);
-
- // The 'Show Browser Action' button.
- if (extension.enable_show_button) {
- var showButton = node.querySelector('.show-button');
- showButton.addEventListener('click', function(e) {
- chrome.send('extensionSettingsShowButton', [extension.id]);
- });
- showButton.hidden = false;
- }
-
- // The 'allow in incognito' checkbox.
- node.querySelector('.incognito-control').hidden =
- !this.data_.incognitoAvailable;
- var incognito = node.querySelector('.incognito-control input');
- incognito.disabled = !extension.incognitoCanBeEnabled;
- incognito.checked = extension.enabledIncognito;
- if (!incognito.disabled) {
- incognito.addEventListener('change', function(e) {
- var checked = e.target.checked;
- butterBarVisibility[extension.id] = checked;
- butterBar.hidden = !checked || extension.is_hosted_app;
- chrome.send('extensionSettingsEnableIncognito',
- [extension.id, String(checked)]);
- });
- }
- var butterBar = node.querySelector('.butter-bar');
- butterBar.hidden = !butterBarVisibility[extension.id];
-
- // The 'collect errors' checkbox. This should only be visible if the
- // error console is enabled - we can detect this by the existence of the
- // |errorCollectionEnabled| property.
- if (extension.wantsErrorCollection) {
- node.querySelector('.error-collection-control').hidden = false;
- var errorCollection =
- node.querySelector('.error-collection-control input');
- errorCollection.checked = extension.errorCollectionEnabled;
- errorCollection.addEventListener('change', function(e) {
- chrome.send('extensionSettingsEnableErrorCollection',
- [extension.id, String(e.target.checked)]);
- });
- }
-
- // The 'allow on all urls' checkbox. This should only be visible if
- // active script restrictions are enabled. If they are not enabled, no
- // extensions should want all urls.
- if (extension.wantsAllUrls) {
- var allUrls = node.querySelector('.all-urls-control');
- allUrls.addEventListener('click', function(e) {
- chrome.send('extensionSettingsAllowOnAllUrls',
- [extension.id, String(e.target.checked)]);
- });
- allUrls.querySelector('input').checked = extension.allowAllUrls;
- allUrls.hidden = false;
- }
-
- // The 'allow file:// access' checkbox.
- if (extension.wantsFileAccess) {
- var fileAccess = node.querySelector('.file-access-control');
- fileAccess.addEventListener('click', function(e) {
- chrome.send('extensionSettingsAllowFileAccess',
- [extension.id, String(e.target.checked)]);
- });
- fileAccess.querySelector('input').checked = extension.allowFileAccess;
- fileAccess.hidden = false;
- }
-
- // The 'Options' button or link, depending on its behaviour.
- if (extension.enabled && extension.optionsUrl) {
- var options, optionsClickListener;
- if (extension.optionsOpenInTab) {
- options = node.querySelector('.options-link');
- // Set an href to get the correct mouse-over appearance (link,
- // footer) - but the actual link opening is done through chrome.send
- // with a preventDefault().
- options.setAttribute('href', extension.optionsPageHref);
- optionsClickListener = function() {
- chrome.send('extensionSettingsOptions', [extension.id]);
- };
- } else {
- options = node.querySelector('.options-button');
- optionsClickListener = function() {
- this.showEmbeddedExtensionOptions_(extension.id, false);
- }.bind(this);
- }
- options.addEventListener('click', function(e) {
- optionsClickListener();
- e.preventDefault();
- });
- options.hidden = false;
- }
-
- // The 'Permissions' link.
- var permissions = node.querySelector('.permissions-link');
- permissions.addEventListener('click', function(e) {
- chrome.send('extensionSettingsPermissions', [extension.id]);
- e.preventDefault();
- });
-
- // The 'View in Web Store/View Web Site' link.
- if (extension.homepageUrl && !extension.enableExtensionInfoDialog) {
- var siteLink = node.querySelector('.site-link');
- siteLink.href = extension.homepageUrl;
- siteLink.textContent = loadTimeData.getString(
- extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
- 'extensionSettingsVisitWebStore');
- siteLink.hidden = false;
- }
-
- if (extension.allow_reload) {
- // The 'Reload' link.
- var reload = node.querySelector('.reload-link');
- reload.addEventListener('click', function(e) {
- chrome.send('extensionSettingsReload', [extension.id]);
- extensionReloadedTimestamp[extension.id] = Date.now();
- });
- reload.hidden = false;
-
- if (extension.is_platform_app) {
- // The 'Launch' link.
- var launch = node.querySelector('.launch-link');
- launch.addEventListener('click', function(e) {
- chrome.send('extensionSettingsLaunch', [extension.id]);
- });
- launch.hidden = false;
- }
- }
-
- if (extension.terminated) {
- var terminatedReload = node.querySelector('.terminated-reload-link');
- terminatedReload.hidden = false;
- terminatedReload.onclick = function() {
- chrome.send('extensionSettingsReload', [extension.id]);
- };
- } else if (extension.corruptInstall && extension.isFromStore) {
- var repair = node.querySelector('.corrupted-repair-button');
- repair.hidden = false;
- repair.onclick = function() {
- chrome.send('extensionSettingsRepair', [extension.id]);
- };
- } else {
- // The 'Enabled' checkbox.
- var enable = node.querySelector('.enable-checkbox');
- enable.hidden = false;
- var enableCheckboxDisabled = extension.managedInstall ||
- extension.suspiciousInstall ||
- extension.corruptInstall ||
- extension.updateRequiredByPolicy ||
- extension.dependentExtensions.length > 0;
- enable.querySelector('input').disabled = enableCheckboxDisabled;
-
- if (extension.managedInstall) {
- var indicator = new cr.ui.ControlledIndicator();
- indicator.classList.add('controlled-extension-indicator');
- indicator.setAttribute('controlled-by', 'policy');
- indicator.setAttribute('textpolicy', extension.policyText || '');
- node.querySelector('.enable-controls').appendChild(indicator);
- }
-
- if (!enableCheckboxDisabled) {
- enable.addEventListener('click', function(e) {
- // When e.target is the label instead of the checkbox, it doesn't
- // have the checked property and the state of the checkbox is
- // left unchanged.
- var checked = e.target.checked;
- if (checked == undefined)
- checked = !e.currentTarget.querySelector('input').checked;
- chrome.send('extensionSettingsEnable',
- [extension.id, checked ? 'true' : 'false']);
-
- // This may seem counter-intuitive (to not set/clear the checkmark)
- // but this page will be updated asynchronously if the extension
- // becomes enabled/disabled. It also might not become enabled or
- // disabled, because the user might e.g. get prompted when enabling
- // and choose not to.
- e.preventDefault();
- });
- }
-
- enable.querySelector('input').checked = extension.enabled;
- }
-
- // 'Remove' button.
- var trashTemplate = $('template-collection').querySelector('.trash');
- var trash = trashTemplate.cloneNode(true);
- trash.title = loadTimeData.getString('extensionUninstall');
- trash.addEventListener('click', function(e) {
- butterBarVisibility[extension.id] = false;
- chrome.send('extensionSettingsUninstall', [extension.id]);
- });
- node.querySelector('.enable-controls').appendChild(trash);
-
- // Developer mode ////////////////////////////////////////////////////////
-
- // First we have the id.
- var idLabel = node.querySelector('.extension-id');
- idLabel.textContent = ' ' + extension.id;
-
- // Then the path, if provided by unpacked extension.
- if (extension.isUnpacked) {
- var loadPath = node.querySelector('.load-path');
- loadPath.hidden = false;
- var pathLink = loadPath.querySelector('a:nth-of-type(1)');
- pathLink.textContent = ' ' + extension.prettifiedPath;
- pathLink.addEventListener('click', function(e) {
- chrome.send('extensionSettingsShowPath', [String(extension.id)]);
- e.preventDefault();
- });
- }
-
- // Then the 'managed, cannot uninstall/disable' message.
- if (extension.managedInstall || extension.recommendedInstall) {
- node.querySelector('.managed-message').hidden = false;
- } else {
- if (extension.suspiciousInstall) {
- // Then the 'This isn't from the webstore, looks suspicious' message.
- node.querySelector('.suspicious-install-message').hidden = false;
- }
- if (extension.corruptInstall) {
- // Then the 'This is a corrupt extension' message.
- node.querySelector('.corrupt-install-message').hidden = false;
- }
- }
-
- // Then the 'An update required by enterprise policy' message. Note that
- // a force-installed extension might be disabled due to being outdated
- // as well.
- if (extension.updateRequiredByPolicy) {
- node.querySelector('.update-required-message').hidden = false;
- // We would like to hide managed installed message since this
- // extension is disabled.
- node.querySelector('.managed-message').hidden = true;
- }
-
- if (extension.dependentExtensions.length > 0) {
- node.classList.add('developer-extras');
- var dependentMessage =
- node.querySelector('.dependent-extensions-message');
- dependentMessage.hidden = false;
- var dependentList = dependentMessage.querySelector('ul');
- var dependentTemplate = $('template-collection').querySelector(
- '.dependent-list-item');
- extension.dependentExtensions.forEach(function(elem) {
- var depNode = dependentTemplate.cloneNode(true);
- depNode.querySelector('.dep-extension-title').textContent = elem.name;
- depNode.querySelector('.dep-extension-id').textContent = elem.id;
- dependentList.appendChild(depNode);
- });
- }
-
- // Then active views.
- if (extension.views.length > 0) {
- var activeViews = node.querySelector('.active-views');
- activeViews.hidden = false;
- var link = activeViews.querySelector('a');
-
- extension.views.forEach(function(view, i) {
- var displayName = view.generatedBackgroundPage ?
- loadTimeData.getString('backgroundPage') : view.path;
- var label = displayName +
- (view.incognito ?
- ' ' + loadTimeData.getString('viewIncognito') : '') +
- (view.renderProcessId == -1 ?
- ' ' + loadTimeData.getString('viewInactive') : '');
- link.textContent = label;
- link.addEventListener('click', function(e) {
- // TODO(estade): remove conversion to string?
- chrome.send('extensionSettingsInspect', [
- String(extension.id),
- String(view.renderProcessId),
- String(view.renderViewId),
- view.incognito
- ]);
- });
-
- if (i < extension.views.length - 1) {
- link = link.cloneNode(true);
- activeViews.appendChild(link);
- }
- });
- }
+ addListener_: function(type, node, query, onclick) {
+ node.querySelector(query).addEventListener(type, onclick);
+ },
- // The extension warnings (describing runtime issues).
- if (extension.warnings) {
- var panel = node.querySelector('.extension-warnings');
- panel.hidden = false;
- var list = panel.querySelector('ul');
- extension.warnings.forEach(function(warning) {
- list.appendChild(document.createElement('li')).innerText = warning;
- });
- }
+ /**
+ * Updates an element's textContent.
+ * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {string} query A query to select an element in |node|.
+ * @param {string} textContent
+ * @private
+ */
+ setTextContent_: function(node, query, textContent) {
+ node.querySelector(query).textContent = textContent;
+ },
- // If the ErrorConsole is enabled, we should have manifest and/or runtime
- // errors. Otherwise, we may have install warnings. We should not have
- // both ErrorConsole errors and install warnings.
- if (extension.manifestErrors) {
- var panel = node.querySelector('.manifest-errors');
- panel.hidden = false;
- panel.appendChild(new extensions.ExtensionErrorList(
- extension.manifestErrors));
- }
- if (extension.runtimeErrors) {
- var panel = node.querySelector('.runtime-errors');
- panel.hidden = false;
- panel.appendChild(new extensions.ExtensionErrorList(
- extension.runtimeErrors));
- }
- if (extension.installWarnings) {
- var panel = node.querySelector('.install-warnings');
- panel.hidden = false;
- var list = panel.querySelector('ul');
- extension.installWarnings.forEach(function(warning) {
- var li = document.createElement('li');
- li.innerText = warning.message;
- list.appendChild(li);
- });
- }
+ /**
+ * Updates an element's visibility and calls |callback| if it is visible.
+ * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {string} query A query to select an element in |node|.
+ * @param {boolean} visible Whether the element should be visible or not.
+ * @param {function({Element} item)} callback Callback if the element
+ * is visible.
not at google - send to devlin 2015/02/10 22:14:37 Mention that the Element it's called with is the i
hcarmona 2015/02/11 02:22:07 Done.
+ * @private
+ */
+ updateElement_: function(node, query, visible, callback) {
not at google - send to devlin 2015/02/10 22:14:37 A more descriptive name would be updateVisibility
hcarmona 2015/02/11 02:22:07 Done.
+ var item = node.querySelector(query);
not at google - send to devlin 2015/02/10 22:14:37 Can you assert that the element existed?
hcarmona 2015/02/11 02:22:07 Done.
+ item.hidden = !visible;
+ if (visible && callback)
+ callback(item);
+ },
- this.appendChild(node);
- if (location.hash.substr(1) == extension.id) {
- // Scroll beneath the fixed header so that the extension is not
- // obscured.
- var topScroll = node.offsetTop - $('page-header').offsetHeight;
- var pad = parseInt(window.getComputedStyle(node, null).marginTop, 10);
- if (!isNaN(pad))
- topScroll -= pad / 2;
- setScrollTopForDocument(document, topScroll);
- }
+ /**
+ * Updates an element to show a list of errors.
+ * @param {Element} panel An element to hold the errors.
+ * @param {?Array<RuntimeError>} errors The errors to be displayed.
+ * @private
+ */
+ updateErrors_: function(panel, errors) {
+ // TODO(hcarmona): Look into updating the ExtensionErrorList rather than
+ // rebuilding it every time.
+ panel.hidden = !errors;
+ panel.textContent = '';
+ if (errors)
+ panel.appendChild(new extensions.ExtensionErrorList(errors));
},
/**

Powered by Google App Engine
This is Rietveld 408576698