| 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 6083af00dab721f0be646524f37501d0e7fdf0bd..cbef72556f07752e55076f707fddf8d4f73052b4 100644
|
| --- a/chrome/browser/resources/extensions/extension_list.js
|
| +++ b/chrome/browser/resources/extensions/extension_list.js
|
| @@ -65,6 +65,137 @@
|
| */
|
| var ExtensionData;
|
|
|
| +///////////////////////////////////////////////////////////////////////////////
|
| +// ExtensionFocusRow:
|
| +
|
| +/**
|
| + * Provides an implementation for a single column grid.
|
| + * @constructor
|
| + * @extends {cr.ui.FocusRow}
|
| + */
|
| +function ExtensionFocusRow() {}
|
| +
|
| +/**
|
| + * 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';
|
|
|
| @@ -96,6 +227,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}
|
| @@ -115,10 +249,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 = [];
|
|
|
| @@ -138,8 +275,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.
|
| + node.destroy();
|
| + }
|
| }
|
|
|
| var idToHighlight = this.getIdQueryParam_();
|
| @@ -154,6 +294,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) {
|
| + rows[i].updateFocusableElements();
|
| + }
|
| + },
|
| +
|
| /**
|
| * Scrolls the page down to the extension node with the given id.
|
| * @param {string} extensionId The id of the extension to scroll to.
|
| @@ -173,7 +321,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
|
| @@ -182,17 +330,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;
|
| @@ -206,8 +357,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)]);
|
| });
|
| @@ -215,13 +366,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)]);
|
| });
|
| @@ -230,46 +383,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;
|
| chrome.send('extensionSettingsEnable', [extension.id, String(checked)]);
|
|
|
| @@ -285,36 +444,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 = [];
|
| @@ -329,12 +494,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.
|
| @@ -346,62 +511,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(
|
| @@ -410,24 +577,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 ||
|
| @@ -438,7 +605,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;
|
| @@ -451,6 +618,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);
|
| }
|
| @@ -458,11 +627,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;
|
| @@ -472,27 +641,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 = '';
|
| @@ -507,7 +676,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');
|
|
|
| @@ -545,10 +714,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 = '';
|
| @@ -562,13 +736,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 = '';
|
| @@ -584,25 +758,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);
|
| },
|
|
|
| /**
|
| @@ -637,17 +801,29 @@ 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)));
|
| + var errorList =
|
| + new extensions.ExtensionErrorList(assertInstanceof(errors, Array));
|
| +
|
| + panel.appendChild(errorList);
|
| +
|
| + var list = errorList.getListElement();
|
| + if (list)
|
| + list.setAttribute('column-type', columnType + 'list');
|
| +
|
| + var button = errorList.getToggleElement();
|
| + if (button)
|
| + button.setAttribute('column-type', columnType + 'button');
|
| }
|
| },
|
|
|
|
|