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'); |
}, |
/** |