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

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

Issue 916243002: Enable keyboard shortcuts for chrome://extensions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Fix license comment and Rebase 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 008a990754a24175ea472eb453f2ebfa7f616932..0b919eb93db8b1340b81878e7913d7c982cd587e 100644
--- a/chrome/browser/resources/extensions/extension_list.js
+++ b/chrome/browser/resources/extensions/extension_list.js
@@ -66,6 +66,137 @@
*/
var ExtensionData;
+///////////////////////////////////////////////////////////////////////////////
+// ExtensionFocusRow:
+
+/**
+ * Provides an implementation for a single column grid.
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+function ExtensionFocusRow() {}
Dan Beam 2015/02/26 03:41:25 next time put this inside the cr.define()
+
+/**
+ * Decorates |focusRow| so that it can be treated as a ExtensionFocusRow.
+ * @param {Element} focusRow The element that has all the columns.
+ * @param {Node} boundary Focus events are ignored outside of this node.
+ */
+ExtensionFocusRow.decorate = function(focusRow, boundary) {
+ focusRow.__proto__ = ExtensionFocusRow.prototype;
+ focusRow.decorate(boundary);
+};
+
+ExtensionFocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ getEquivalentElement: function(element) {
+ if (this.focusableElements.indexOf(element) > -1)
+ return element;
+
+ // All elements default to another element with the same type.
+ var columnType = element.getAttribute('column-type');
+ var equivalent = this.querySelector('[column-type=' + columnType + ']');
+
+ if (!equivalent || !this.canAddElement_(equivalent)) {
+ var actionLinks = ['options', 'website', 'launch', 'localReload'];
+ var optionalControls = ['showButton', 'incognito', 'dev-collectErrors',
+ 'allUrls', 'localUrls'];
+ var removeStyleButtons = ['trash', 'enterprise'];
+ var enableControls = ['terminatedReload', 'repair', 'enabled'];
+
+ if (actionLinks.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(actionLinks);
+ else if (optionalControls.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(optionalControls);
+ else if (removeStyleButtons.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(removeStyleButtons);
+ else if (enableControls.indexOf(columnType) > -1)
+ equivalent = this.getFirstFocusableByType_(enableControls);
+ }
+
+ // Return the first focusable element if no equivalent type is found.
+ return equivalent || this.focusableElements[0];
+ },
+
+ /** Updates the list of focusable elements. */
+ updateFocusableElements: function() {
+ this.focusableElements.length = 0;
+
+ var focusableCandidates = this.querySelectorAll('[column-type]');
+ for (var i = 0; i < focusableCandidates.length; ++i) {
+ var element = focusableCandidates[i];
+ if (this.canAddElement_(element))
+ this.addFocusableElement(element);
+ }
+ },
+
+ /**
+ * Get the first focusable element that matches a list of types.
+ * @param {Array<string>} types An array of types to match from.
+ * @return {?Element} Return the first element that matches a type in |types|.
+ * @private
+ */
+ getFirstFocusableByType_: function(types) {
+ for (var i = 0; i < this.focusableElements.length; ++i) {
+ var element = this.focusableElements[i];
+ if (types.indexOf(element.getAttribute('column-type')) > -1)
+ return element;
+ }
+ return null;
+ },
+
+ /**
+ * Setup a typical column in the ExtensionFocusRow. A column can be any
+ * element and should have an action when clicked/toggled. This function
+ * adds a listener and a handler for an event. Also adds the "column-type"
+ * attribute to make the element focusable in |updateFocusableElements|.
+ * @param {string} query A query to select the element to set up.
+ * @param {string} columnType A tag used to identify the column when
+ * changing focus.
+ * @param {string} eventType The type of event to listen to.
+ * @param {function(Event)} handler The function that should be called
+ * by the event.
+ * @private
+ */
+ setupColumn: function(query, columnType, eventType, handler) {
+ var element = this.querySelector(query);
+ element.addEventListener(eventType, handler);
+ element.setAttribute('column-type', columnType);
+ },
+
+ /**
+ * @param {Element} element
+ * @return {boolean}
+ * @private
+ */
+ canAddElement_: function(element) {
+ if (!element || element.disabled)
+ return false;
+
+ var developerMode = $('extension-settings').classList.contains('dev-mode');
+ if (this.isDeveloperOption_(element) && !developerMode)
+ return false;
+
+ for (var el = element; el; el = el.parentElement) {
+ if (el.hidden)
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Returns true if the element should only be shown in developer mode.
+ * @param {Element} element
+ * @return {boolean}
+ * @private
+ */
+ isDeveloperOption_: function(element) {
+ return /^dev-/.test(element.getAttribute('column-type'));
+ },
+};
+
cr.define('options', function() {
'use strict';
@@ -97,6 +228,9 @@ cr.define('options', function() {
*/
optionsShown_: false,
+ /** @private {!cr.ui.FocusGrid} */
+ focusGrid_: new cr.ui.FocusGrid(),
+
/**
* Necessary to only show the butterbar once.
* @private {boolean}
@@ -116,10 +250,13 @@ cr.define('options', function() {
},
/**
- * Creates all extension items from scratch.
+ * Creates or updates all extension items from scratch.
* @private
*/
showExtensionNodes_: function() {
+ // Remove the rows from |focusGrid_| without destroying them.
+ this.focusGrid_.rows.length = 0;
+
// Any node that is not updated will be removed.
var seenIds = [];
@@ -139,8 +276,11 @@ cr.define('options', function() {
var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]');
for (var i = 0; i < nodes.length; ++i) {
var node = nodes[i];
- if (seenIds.indexOf(node.id) < 0)
+ if (seenIds.indexOf(node.id) < 0) {
node.parentElement.removeChild(node);
+ // Unregister the removed node from events.
+ assertInstanceof(node, ExtensionFocusRow).destroy();
+ }
}
var idToHighlight = this.getIdQueryParam_();
@@ -155,6 +295,14 @@ cr.define('options', function() {
this.classList.toggle('empty-extension-list', noExtensions);
},
+ /** Updates each row's focusable elements without rebuilding the grid. */
+ updateFocusableElements: function() {
+ var rows = document.querySelectorAll('.extension-list-item-wrapper[id]');
+ for (var i = 0; i < rows.length; ++i) {
+ assertInstanceof(rows[i], ExtensionFocusRow).updateFocusableElements();
+ }
+ },
+
/**
* Scrolls the page down to the extension node with the given id.
* @param {string} extensionId The id of the extension to scroll to.
@@ -174,7 +322,7 @@ cr.define('options', function() {
/**
* Synthesizes and initializes an HTML element for the extension metadata
* given in |extension|.
- * @param {ExtensionData} extension A dictionary of extension metadata.
+ * @param {!ExtensionData} extension A dictionary of extension metadata.
* @param {?Element} nextNode |node| should be inserted before |nextNode|.
* |node| will be appended to the end if |nextNode| is null.
* @private
@@ -183,17 +331,20 @@ cr.define('options', function() {
var template = $('template-collection').querySelector(
'.extension-list-item-wrapper');
var node = template.cloneNode(true);
- node.id = extension.id;
+ ExtensionFocusRow.decorate(node, $('extension-settings-list'));
+
+ var row = assertInstanceof(node, ExtensionFocusRow);
+ row.id = extension.id;
// The 'Show Browser Action' button.
- this.addListener_('click', node, '.show-button', function(e) {
+ row.setupColumn('.show-button', 'showButton', 'click', function(e) {
chrome.send('extensionSettingsShowButton', [extension.id]);
});
// The 'allow in incognito' checkbox.
- this.addListener_('change', node, '.incognito-control input',
- function(e) {
- var butterBar = node.querySelector('.butter-bar');
+ row.setupColumn('.incognito-control input', 'incognito', 'change',
+ function(e) {
+ var butterBar = row.querySelector('.butter-bar');
var checked = e.target.checked;
if (!this.butterbarShown_) {
butterBar.hidden = !checked || extension.is_hosted_app;
@@ -207,8 +358,8 @@ cr.define('options', function() {
// 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.
- this.addListener_('change', node, '.error-collection-control input',
- function(e) {
+ row.setupColumn('.error-collection-control input', 'dev-collectErrors',
+ 'change', function(e) {
chrome.send('extensionSettingsEnableErrorCollection',
[extension.id, String(e.target.checked)]);
});
@@ -216,13 +367,15 @@ cr.define('options', function() {
// 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.
- this.addListener_('click', node, '.all-urls-control', function(e) {
+ row.setupColumn('.all-urls-control input', 'allUrls', 'click',
+ function(e) {
chrome.send('extensionSettingsAllowOnAllUrls',
[extension.id, String(e.target.checked)]);
});
// The 'allow file:// access' checkbox.
- this.addListener_('click', node, '.file-access-control', function(e) {
+ row.setupColumn('.file-access-control input', 'localUrls', 'click',
+ function(e) {
chrome.send('extensionSettingsAllowFileAccess',
[extension.id, String(e.target.checked)]);
});
@@ -231,46 +384,52 @@ cr.define('options', function() {
// 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().
- node.querySelector('.options-link').href = extension.optionsPageHref;
- this.addListener_('click', node, '.options-link', function(e) {
+ row.querySelector('.options-link').href = extension.optionsPageHref;
+ row.setupColumn('.options-link', 'options', 'click', function(e) {
chrome.send('extensionSettingsOptions', [extension.id]);
e.preventDefault();
});
- this.addListener_('click', node, '.options-button', function(e) {
+ row.setupColumn('.options-button', 'options', 'click', function(e) {
this.showEmbeddedExtensionOptions_(extension.id, false);
e.preventDefault();
}.bind(this));
+ // The 'View in Web Store/View Web Site' link.
+ row.querySelector('.site-link').setAttribute('column-type', 'website');
+
// The 'Permissions' link.
- this.addListener_('click', node, '.permissions-link', function(e) {
+ row.setupColumn('.permissions-link', 'details', 'click', function(e) {
chrome.send('extensionSettingsPermissions', [extension.id]);
e.preventDefault();
});
// The 'Reload' link.
- this.addListener_('click', node, '.reload-link', function(e) {
+ row.setupColumn('.reload-link', 'localReload', 'click', function(e) {
chrome.send('extensionSettingsReload', [extension.id]);
extensionReloadedTimestamp[extension.id] = Date.now();
});
// The 'Launch' link.
- this.addListener_('click', node, '.launch-link', function(e) {
+ row.setupColumn('.launch-link', 'launch', 'click', function(e) {
chrome.send('extensionSettingsLaunch', [extension.id]);
});
// The 'Reload' terminated link.
- this.addListener_('click', node, '.terminated-reload-link', function(e) {
+ row.setupColumn('.terminated-reload-link', 'terminatedReload', 'click',
+ function(e) {
chrome.send('extensionSettingsReload', [extension.id]);
});
// The 'Repair' corrupted link.
- this.addListener_('click', node, '.corrupted-repair-button', function(e) {
+ row.setupColumn('.corrupted-repair-button', 'repair', 'click',
+ function(e) {
chrome.send('extensionSettingsRepair', [extension.id]);
});
// The 'Enabled' checkbox.
- this.addListener_('change', node, '.enable-checkbox input', function(e) {
+ row.setupColumn('.enable-checkbox input', 'enabled', 'change',
+ function(e) {
var checked = e.target.checked;
// TODO(devlin): What should we do if this fails?
chrome.management.setEnabled(extension.id, checked);
@@ -287,36 +446,42 @@ cr.define('options', function() {
var trashTemplate = $('template-collection').querySelector('.trash');
var trash = trashTemplate.cloneNode(true);
trash.title = loadTimeData.getString('extensionUninstall');
+ trash.hidden = extension.managedInstall;
+ trash.setAttribute('column-type', 'trash');
trash.addEventListener('click', function(e) {
chrome.send('extensionSettingsUninstall', [extension.id]);
});
- node.querySelector('.enable-controls').appendChild(trash);
+ row.querySelector('.enable-controls').appendChild(trash);
// Developer mode ////////////////////////////////////////////////////////
// The path, if provided by unpacked extension.
- this.addListener_('click', node, '.load-path a:first-of-type',
- function(e) {
+ row.setupColumn('.load-path a:first-of-type', 'dev-loadPath', 'click',
+ function(e) {
chrome.send('extensionSettingsShowPath', [String(extension.id)]);
e.preventDefault();
});
// Maintain the order that nodes should be in when creating as well as
- // when adding only one new node.
- this.insertBefore(node, nextNode);
- this.updateNode_(extension, node);
+ // when adding only one new row.
+ this.insertBefore(row, nextNode);
+ this.updateNode_(extension, row);
},
/**
* Updates an HTML element for the extension metadata given in |extension|.
- * @param {ExtensionData} extension A dictionary of extension metadata.
- * @param {Element} node The node that is being updated.
+ * @param {!ExtensionData} extension A dictionary of extension metadata.
+ * @param {!ExtensionFocusRow} row The node that is being updated.
* @private
*/
- updateNode_: function(extension, node) {
- node.classList.toggle('inactive-extension',
- !extension.enabled || extension.terminated);
+ updateNode_: function(extension, row) {
+ var isActive = extension.enabled && !extension.terminated;
+
+ row.classList.toggle('inactive-extension', !isActive);
+ // Hack to keep the closure compiler happy about |remove|.
+ // TODO(hcarmona): Remove this hack when the closure compiler is updated.
+ var node = /** @type {Element} */ (row);
node.classList.remove('policy-controlled', 'may-not-modify',
'may-not-remove');
var classes = [];
@@ -331,12 +496,12 @@ cr.define('options', function() {
extension.updateRequiredByPolicy) {
classes.push('may-not-modify');
}
- node.classList.add.apply(node.classList, classes);
+ row.classList.add.apply(row.classList, classes);
- node.classList.toggle('extension-highlight',
- node.id == this.getIdQueryParam_());
+ row.classList.toggle('extension-highlight',
+ row.id == this.getIdQueryParam_());
- var item = node.querySelector('.extension-list-item');
+ var item = row.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.
@@ -348,62 +513,64 @@ cr.define('options', function() {
item.style.backgroundImage = 'url(' + extension.icon + ')';
}
- this.setText_(node, '.extension-title', extension.name);
- this.setText_(node, '.extension-version', extension.version);
- this.setText_(node, '.location-text', extension.locationText);
- this.setText_(node, '.blacklist-text', extension.blacklistText);
- this.setText_(node, '.extension-description',
- extension.description);
+ this.setText_(row, '.extension-title', extension.name);
+ this.setText_(row, '.extension-version', extension.version);
+ this.setText_(row, '.location-text', extension.locationText);
+ this.setText_(row, '.blacklist-text', extension.blacklistText);
+ this.setText_(row, '.extension-description', extension.description);
// The 'Show Browser Action' button.
- this.updateVisibility_(node, '.show-button',
- extension.enable_show_button);
+ this.updateVisibility_(row, '.show-button',
+ isActive && extension.enable_show_button);
// The 'allow in incognito' checkbox.
- this.updateVisibility_(node, '.incognito-control',
- this.data_.incognitoAvailable, function(item) {
+ this.updateVisibility_(row, '.incognito-control',
+ isActive && this.data_.incognitoAvailable,
+ function(item) {
var incognito = item.querySelector('input');
incognito.disabled = !extension.incognitoCanBeEnabled;
incognito.checked = extension.enabledIncognito;
});
// Hide butterBar if incognito is not enabled for the extension.
- var butterBar = node.querySelector('.butter-bar');
+ var butterBar = row.querySelector('.butter-bar');
butterBar.hidden = butterBar.hidden || !extension.enabledIncognito;
// 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.
- this.updateVisibility_(node, '.error-collection-control',
- extension.wantsErrorCollection, function(item) {
+ this.updateVisibility_(row, '.error-collection-control',
+ isActive && extension.wantsErrorCollection,
+ function(item) {
item.querySelector('input').checked = extension.errorCollectionEnabled;
});
// 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.
- this.updateVisibility_(node, '.all-urls-control', extension.showAllUrls,
- function(item) {
+ this.updateVisibility_(row, '.all-urls-control',
+ isActive && extension.showAllUrls, function(item) {
item.querySelector('input').checked = extension.allowAllUrls;
});
// The 'allow file:// access' checkbox.
- this.updateVisibility_(node, '.file-access-control',
- extension.wantsFileAccess, function(item) {
+ this.updateVisibility_(row, '.file-access-control',
+ isActive && extension.wantsFileAccess,
+ function(item) {
item.querySelector('input').checked = extension.allowFileAccess;
});
// The 'Options' button or link, depending on its behaviour.
var optionsEnabled = extension.enabled && !!extension.optionsUrl;
- this.updateVisibility_(node, '.options-link', optionsEnabled &&
+ this.updateVisibility_(row, '.options-link', optionsEnabled &&
extension.optionsOpenInTab);
- this.updateVisibility_(node, '.options-button', optionsEnabled &&
+ this.updateVisibility_(row, '.options-button', optionsEnabled &&
!extension.optionsOpenInTab);
// The 'View in Web Store/View Web Site' link.
var siteLinkEnabled = !!extension.homepageUrl &&
!extension.enableExtensionInfoDialog;
- this.updateVisibility_(node, '.site-link', siteLinkEnabled,
+ this.updateVisibility_(row, '.site-link', siteLinkEnabled,
function(item) {
item.href = extension.homepageUrl;
item.textContent = loadTimeData.getString(
@@ -412,24 +579,24 @@ cr.define('options', function() {
});
// The 'Reload' link.
- this.updateVisibility_(node, '.reload-link', extension.allow_reload);
+ this.updateVisibility_(row, '.reload-link', extension.allow_reload);
// The 'Launch' link.
- this.updateVisibility_(node, '.launch-link', extension.allow_reload &&
+ this.updateVisibility_(row, '.launch-link', extension.allow_reload &&
extension.is_platform_app);
// The 'Reload' terminated link.
var isTerminated = extension.terminated;
- this.updateVisibility_(node, '.terminated-reload-link', isTerminated);
+ this.updateVisibility_(row, '.terminated-reload-link', isTerminated);
// The 'Repair' corrupted link.
var canRepair = !isTerminated && extension.corruptInstall &&
extension.isFromStore;
- this.updateVisibility_(node, '.corrupted-repair-button', canRepair);
+ this.updateVisibility_(row, '.corrupted-repair-button', canRepair);
// The 'Enabled' checkbox.
var isOK = !isTerminated && !canRepair;
- this.updateVisibility_(node, '.enable-checkbox', isOK, function(item) {
+ this.updateVisibility_(row, '.enable-checkbox', isOK, function(item) {
var enableCheckboxDisabled = extension.managedInstall ||
extension.suspiciousInstall ||
extension.corruptInstall ||
@@ -441,7 +608,7 @@ cr.define('options', function() {
});
// Button for extensions controlled by policy.
- var controlNode = node.querySelector('.enable-controls');
+ var controlNode = row.querySelector('.enable-controls');
var indicator =
controlNode.querySelector('.controlled-extension-indicator');
var needsIndicator = isOK && extension.managedInstall;
@@ -454,6 +621,8 @@ cr.define('options', function() {
indicator.setAttribute('textpolicy', textPolicy);
indicator.image.setAttribute('aria-label', textPolicy);
controlNode.appendChild(indicator);
+ indicator.querySelector('div').setAttribute('column-type',
+ 'enterprise');
} else if (!needsIndicator && indicator) {
controlNode.removeChild(indicator);
}
@@ -461,11 +630,11 @@ cr.define('options', function() {
// Developer mode ////////////////////////////////////////////////////////
// First we have the id.
- var idLabel = node.querySelector('.extension-id');
+ var idLabel = row.querySelector('.extension-id');
idLabel.textContent = ' ' + extension.id;
// Then the path, if provided by unpacked extension.
- this.updateVisibility_(node, '.load-path', extension.isUnpacked,
+ this.updateVisibility_(row, '.load-path', extension.isUnpacked,
function(item) {
item.querySelector('a:first-of-type').textContent =
' ' + extension.prettifiedPath;
@@ -475,27 +644,27 @@ cr.define('options', function() {
// We would like to hide managed installed message since this
// extension is disabled.
var isRequired = extension.managedInstall || extension.recommendedInstall;
- this.updateVisibility_(node, '.managed-message', isRequired &&
+ this.updateVisibility_(row, '.managed-message', isRequired &&
!extension.updateRequiredByPolicy);
// Then the 'This isn't from the webstore, looks suspicious' message.
- this.updateVisibility_(node, '.suspicious-install-message', !isRequired &&
+ this.updateVisibility_(row, '.suspicious-install-message', !isRequired &&
extension.suspiciousInstall);
// Then the 'This is a corrupt extension' message.
- this.updateVisibility_(node, '.corrupt-install-message', !isRequired &&
+ this.updateVisibility_(row, '.corrupt-install-message', !isRequired &&
extension.corruptInstall);
// Then the 'An update required by enterprise policy' message. Note that
// a force-installed extension might be disabled due to being outdated
// as well.
- this.updateVisibility_(node, '.update-required-message',
+ this.updateVisibility_(row, '.update-required-message',
extension.updateRequiredByPolicy);
// The 'following extensions depend on this extension' list.
var hasDependents = extension.dependentExtensions.length > 0;
- node.classList.toggle('developer-extras', hasDependents);
- this.updateVisibility_(node, '.dependent-extensions-message',
+ row.classList.toggle('developer-extras', hasDependents);
+ this.updateVisibility_(row, '.dependent-extensions-message',
hasDependents, function(item) {
var dependentList = item.querySelector('ul');
dependentList.textContent = '';
@@ -510,7 +679,7 @@ cr.define('options', function() {
});
// The active views.
- this.updateVisibility_(node, '.active-views', extension.views.length > 0,
+ this.updateVisibility_(row, '.active-views', extension.views.length > 0,
function(item) {
var link = item.querySelector('a');
@@ -548,10 +717,15 @@ cr.define('options', function() {
item.appendChild(link);
}
});
+
+ var allLinks = item.querySelectorAll('a');
+ for (var i = 0; i < allLinks.length; ++i) {
+ allLinks[i].setAttribute('column-type', 'dev-activeViews' + i);
+ }
});
// The extension warnings (describing runtime issues).
- this.updateVisibility_(node, '.extension-warnings', !!extension.warnings,
+ this.updateVisibility_(row, '.extension-warnings', !!extension.warnings,
function(item) {
var warningList = item.querySelector('ul');
warningList.textContent = '';
@@ -565,13 +739,13 @@ cr.define('options', function() {
// errors. Otherwise, we may have install warnings. We should not have
// both ErrorConsole errors and install warnings.
// Errors.
- this.updateErrors_(node.querySelector('.manifest-errors'),
- extension.manifestErrors);
- this.updateErrors_(node.querySelector('.runtime-errors'),
- extension.runtimeErrors);
+ this.updateErrors_(row.querySelector('.manifest-errors'),
+ 'dev-manifestErrors', extension.manifestErrors);
+ this.updateErrors_(row.querySelector('.runtime-errors'),
+ 'dev-runtimeErrors', extension.runtimeErrors);
// Install warnings.
- this.updateVisibility_(node, '.install-warnings',
+ this.updateVisibility_(row, '.install-warnings',
!!extension.installWarnings, function(item) {
var installWarningList = item.querySelector('ul');
installWarningList.textContent = '';
@@ -587,25 +761,15 @@ cr.define('options', function() {
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);
+ var topScroll = row.offsetTop - $('page-header').offsetHeight;
+ var pad = parseInt(window.getComputedStyle(row, null).marginTop, 10);
if (!isNaN(pad))
topScroll -= pad / 2;
setScrollTopForDocument(document, topScroll);
}
- },
- /**
- * 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)} handler The function that should be called
- * on click.
- * @private
- */
- addListener_: function(type, node, query, handler) {
- node.querySelector(query).addEventListener(type, handler);
+ row.updateFocusableElements();
+ this.focusGrid_.addRow(row);
},
/**
@@ -640,18 +804,32 @@ cr.define('options', function() {
/**
* Updates an element to show a list of errors.
* @param {Element} panel An element to hold the errors.
+ * @param {string} columnType A tag used to identify the column when
+ * changing focus.
* @param {Array<RuntimeError>|undefined} errors The errors to be displayed.
* @private
*/
- updateErrors_: function(panel, errors) {
+ updateErrors_: function(panel, columnType, errors) {
// TODO(hcarmona): Look into updating the ExtensionErrorList rather than
// rebuilding it every time.
panel.hidden = !errors || errors.length == 0;
panel.textContent = '';
- if (!panel.hidden) {
- panel.appendChild(
- new extensions.ExtensionErrorList(assertInstanceof(errors, Array)));
- }
+
+ if (panel.hidden)
+ return;
+
+ var errorList =
+ new extensions.ExtensionErrorList(assertInstanceof(errors, Array));
+
+ panel.appendChild(errorList);
+
+ var list = errorList.getErrorListElement();
+ if (list)
+ list.setAttribute('column-type', columnType + 'list');
+
+ var button = errorList.getToggleElement();
+ if (button)
+ button.setAttribute('column-type', columnType + 'button');
},
/**

Powered by Google App Engine
This is Rietveld 408576698