Chromium Code Reviews| 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)); |
| }, |
| /** |