summaryrefslogtreecommitdiffstats
path: root/chromium/chrome/browser/resources/extensions
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-10-13 13:24:50 +0200
committerAllan Sandfeld Jensen <allan.jensen@theqtcompany.com>2015-10-14 10:57:25 +0000
commitaf3d4809763ef308f08ced947a73b624729ac7ea (patch)
tree4402b911e30383f6c6dace1e8cf3b8e85355db3a /chromium/chrome/browser/resources/extensions
parent0e8ff63a407fe323e215bb1a2c423c09a4747c8a (diff)
BASELINE: Update Chromium to 47.0.2526.14
Also adding in sources needed for spellchecking. Change-Id: Idd44170fa1616f26315188970a8d5ba7d472b18a Reviewed-by: Michael BrĂ¼ning <michael.bruning@theqtcompany.com>
Diffstat (limited to 'chromium/chrome/browser/resources/extensions')
-rw-r--r--chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css3
-rw-r--r--chromium/chrome/browser/resources/extensions/compiled_resources.gyp1
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.css2
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.html8
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_error.js99
-rw-r--r--chromium/chrome/browser/resources/extensions/extension_list.js671
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.css3
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.html2
-rw-r--r--chromium/chrome/browser/resources/extensions/extensions.js23
-rw-r--r--chromium/chrome/browser/resources/extensions/focus_row.js30
10 files changed, 382 insertions, 460 deletions
diff --git a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
index 18b0168ed94..6040b4620bf 100644
--- a/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
+++ b/chromium/chrome/browser/resources/extensions/chromeos/kiosk_apps.css
@@ -118,7 +118,8 @@ list .row-delete-button:active {
}
.kiosk-app-icon.spinner {
- background-image: url(chrome://resources/images/throbber.svg) !important;
+ background-image: url(chrome://resources/images/throbber_small.svg)
+ !important;
}
.kiosk-app-name,
diff --git a/chromium/chrome/browser/resources/extensions/compiled_resources.gyp b/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
index e6127bf181a..a40ae066330 100644
--- a/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
+++ b/chromium/chrome/browser/resources/extensions/compiled_resources.gyp
@@ -29,6 +29,7 @@
'../../../../ui/webui/resources/js/event_tracker.js',
'../../../../ui/webui/resources/js/load_time_data.js',
'../../../../ui/webui/resources/js/util.js',
+ 'focus_row.js',
],
'externs': [
'<(EXTERNS_DIR)/chrome_extensions.js',
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.css b/chromium/chrome/browser/resources/extensions/extension_error.css
index d6514114bec..79ee0ab3adf 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.css
+++ b/chromium/chrome/browser/resources/extensions/extension_error.css
@@ -70,7 +70,9 @@
}
.extension-error-metadata > .error-delete-button {
+ -webkit-appearance: none;
background: url(chrome://theme/IDR_CLOSE_DIALOG) center no-repeat;
+ border: none;
height: 14px;
opacity: 0.6;
width: 14px;
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.html b/chromium/chrome/browser/resources/extensions/extension_error.html
index 29615777711..01589bf8c44 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.html
+++ b/chromium/chrome/browser/resources/extensions/extension_error.html
@@ -14,8 +14,8 @@ in the LICENSE file.
<span id="no-errors-span" i18n-content="extensionErrorNoErrors" hidden>
</span>
</div>
- <div class="extension-error-metadata">
- <p class="extension-error-message"></p>
- <div class="error-delete-button"></div>
- </div>
+ <li class="extension-error-metadata">
+ <p class="extension-error-message" tabindex="0"></p>
+ <button class="custom-appearance error-delete-button"></button>
+ </li>
</div>
diff --git a/chromium/chrome/browser/resources/extensions/extension_error.js b/chromium/chrome/browser/resources/extensions/extension_error.js
index c9296554d9a..9ad71e8d753 100644
--- a/chromium/chrome/browser/resources/extensions/extension_error.js
+++ b/chromium/chrome/browser/resources/extensions/extension_error.js
@@ -43,41 +43,25 @@ cr.define('extensions', function() {
* notification to the user when an error is caused by an extension.
* @param {(RuntimeError|ManifestError)} error The error the element should
* represent.
- * @param {Element} boundary The boundary for the focus grid.
* @constructor
- * @extends {cr.ui.FocusRow}
+ * @extends {HTMLElement}
*/
- function ExtensionError(error, boundary) {
+ function ExtensionError(error) {
var div = cloneTemplate('extension-error-metadata');
div.__proto__ = ExtensionError.prototype;
- div.decorateWithError_(error, boundary);
+ div.decorate(error);
return div;
}
ExtensionError.prototype = {
- __proto__: cr.ui.FocusRow.prototype,
-
- /** @override */
- getEquivalentElement: function(element) {
- if (element.classList.contains('extension-error-metadata'))
- return this;
- if (element.classList.contains('error-delete-button')) {
- return /** @type {!HTMLElement} */ (this.querySelector(
- '.error-delete-button'));
- }
- assertNotReached();
- return element;
- },
+ __proto__: HTMLElement.prototype,
/**
* @param {(RuntimeError|ManifestError)} error The error the element should
* represent.
- * @param {Element} boundary The boundary for the FocusGrid.
* @private
*/
- decorateWithError_: function(error, boundary) {
- this.decorate(boundary);
-
+ decorate: function(error) {
/**
* The backing error.
* @type {(ManifestError|RuntimeError)}
@@ -125,13 +109,11 @@ cr.define('extensions', function() {
if (e.target != deleteButton)
this.requestActive_();
}.bind(this));
+
this.addEventListener('keydown', function(e) {
if (e.keyIdentifier == 'Enter' && e.target != deleteButton)
this.requestActive_();
});
-
- this.addFocusableElement(this);
- this.addFocusableElement(this.querySelector('.error-delete-button'));
},
/**
@@ -161,6 +143,23 @@ cr.define('extensions', function() {
return div;
}
+ /**
+ * @param {!Element} root
+ * @param {?Node} boundary
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+ ExtensionErrorList.FocusRow = function(root, boundary) {
+ cr.ui.FocusRow.call(this, root, boundary);
+
+ this.addItem('message', '.extension-error-message');
+ this.addItem('delete', '.error-delete-button');
+ };
+
+ ExtensionErrorList.FocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+ };
+
ExtensionErrorList.prototype = {
__proto__: HTMLDivElement.prototype,
@@ -169,18 +168,19 @@ cr.define('extensions', function() {
* @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
*/
decorate: function(errors) {
- /**
- * @private {!Array<(ManifestError|RuntimeError)>}
- */
+ /** @private {!Array<(ManifestError|RuntimeError)>} */
this.errors_ = [];
+ /** @private {!cr.ui.FocusGrid} */
this.focusGrid_ = new cr.ui.FocusGrid();
- this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
- this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
- this.gridBoundary_.addEventListener('focusin',
- this.onFocusin_.bind(this));
+
+ /** @private {Element} */
+ this.listContents_ = this.querySelector('.extension-error-list-contents');
+
errors.forEach(this.addError_, this);
+ this.focusGrid_.ensureRowActive();
+
this.addEventListener('highlightExtensionError', function(e) {
this.setActiveErrorNode_(e.target);
});
@@ -228,10 +228,12 @@ cr.define('extensions', function() {
addError_: function(error) {
this.querySelector('#no-errors-span').hidden = true;
this.errors_.push(error);
- var focusRow = new ExtensionError(error, this.gridBoundary_);
- this.gridBoundary_.appendChild(document.createElement('li')).
- appendChild(focusRow);
- this.focusGrid_.addRow(focusRow);
+
+ var extensionError = new ExtensionError(error);
+ this.listContents_.appendChild(extensionError);
+
+ this.focusGrid_.addRow(
+ new ExtensionErrorList.FocusRow(extensionError, this.listContents_));
},
/**
@@ -253,6 +255,14 @@ cr.define('extensions', function() {
this.errors_.splice(index, 1);
var listElement = errorList.children[index];
+
+ var focusRow = this.focusGrid_.getRowForRoot(listElement);
+ this.focusGrid_.removeRow(focusRow);
+ this.focusGrid_.ensureRowActive();
+ focusRow.destroy();
+
+ // TODO(dbeam): in a world where this UI is actually used, we should
+ // probably move the focus before removing |listElement|.
listElement.parentNode.removeChild(listElement);
if (wasActive) {
@@ -357,25 +367,6 @@ cr.define('extensions', function() {
new CustomEvent('activeExtensionErrorChanged',
{bubbles: true, detail: node ? node.error : null}));
},
-
- /**
- * The grid should not be focusable once it or an element inside it is
- * focused. This is necessary to allow tabbing out of the grid in reverse.
- * @private
- */
- onFocusin_: function() {
- this.gridBoundary_.tabIndex = -1;
- },
-
- /**
- * Focus the first focusable row when tabbing into the grid for the
- * first time.
- * @private
- */
- onFocus_: function() {
- var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
- activeRow.getEquivalentElement(null).focus();
- },
};
return {
diff --git a/chromium/chrome/browser/resources/extensions/extension_list.js b/chromium/chrome/browser/resources/extensions/extension_list.js
index d8cc1e03a9a..2bbed63d122 100644
--- a/chromium/chrome/browser/resources/extensions/extension_list.js
+++ b/chromium/chrome/browser/resources/extensions/extension_list.js
@@ -4,148 +4,63 @@
<include src="extension_error.js">
-///////////////////////////////////////////////////////////////////////////////
-// 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];
- },
-
- /** @override */
- makeActive: function(active) {
- cr.ui.FocusRow.prototype.makeActive.call(this, active);
-
- // Only highlight if the row has focus.
- this.classList.toggle('extension-highlight',
- active && this.contains(document.activeElement));
- },
-
- /** 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);
- }
- },
+cr.define('extensions', function() {
+ 'use strict';
- /**
- * 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;
- },
+ var ExtensionType = chrome.developerPrivate.ExtensionType;
/**
- * 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
+ * @param {string} name The name of the template to clone.
+ * @return {!Element} The freshly cloned template.
*/
- setupColumn: function(query, columnType, eventType, handler) {
- var element = this.querySelector(query);
- element.addEventListener(eventType, handler);
- element.setAttribute('column-type', columnType);
- },
+ function cloneTemplate(name) {
+ var node = $('templates').querySelector('.' + name).cloneNode(true);
+ return assertInstanceof(node, Element);
+ }
/**
- * @param {Element} element
- * @return {boolean}
- * @private
+ * @extends {HTMLElement}
+ * @constructor
*/
- canAddElement_: function(element) {
- if (!element || element.disabled)
- return false;
-
- var developerMode = $('extension-settings').classList.contains('dev-mode');
- if (this.isDeveloperOption_(element) && !developerMode)
- return false;
+ function ExtensionWrapper() {
+ var wrapper = cloneTemplate('extension-list-item-wrapper');
+ wrapper.__proto__ = ExtensionWrapper.prototype;
+ wrapper.initialize();
+ return wrapper;
+ }
- for (var el = element; el; el = el.parentElement) {
- if (el.hidden)
- return false;
- }
+ ExtensionWrapper.prototype = {
+ __proto__: HTMLElement.prototype,
- return true;
- },
+ initialize: function() {
+ var boundary = $('extension-settings-list');
+ /** @private {!extensions.FocusRow} */
+ this.focusRow_ = new extensions.FocusRow(this, boundary);
+ },
- /**
- * 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'));
- },
-};
+ /** @return {!cr.ui.FocusRow} */
+ getFocusRow: function() {
+ return this.focusRow_;
+ },
-cr.define('extensions', function() {
- 'use strict';
+ /**
+ * Add an item to the focus row and listen for |eventType| events.
+ * @param {string} focusType A tag used to identify equivalent elements when
+ * changing focus between rows.
+ * @param {string} query A query to select the element to set up.
+ * @param {string=} opt_eventType The type of event to listen to.
+ * @param {function(Event)=} opt_handler The function that should be called
+ * by the event.
+ * @private
+ */
+ setupColumn: function(focusType, query, opt_eventType, opt_handler) {
+ assert(this.focusRow_.addItem(focusType, query));
+ if (opt_eventType) {
+ assert(opt_handler);
+ this.querySelector(query).addEventListener(opt_eventType, opt_handler);
+ }
+ },
+ };
var ExtensionCommandsOverlay = extensions.ExtensionCommandsOverlay;
@@ -225,10 +140,11 @@ cr.define('extensions', function() {
permissionsPromptIsShowing_: false,
/**
- * Necessary to only show the butterbar once.
+ * Whether or not any initial navigation (like scrolling to an extension,
+ * or opening an options page) has occurred.
* @private {boolean}
*/
- butterbarShown_: false,
+ didInitialNavigation_: false,
/**
* Whether or not incognito mode is available.
@@ -253,15 +169,7 @@ cr.define('extensions', function() {
/** @private {!extensions.ExtensionListDelegate} */
this.delegate_ = delegate;
- /**
- * |loadFinished| should be used for testing purposes and will be
- * fulfilled when this list has finished loading the first time.
- * @type {Promise}
- * */
- this.loadFinished = new Promise(function(resolve, reject) {
- /** @private {function(?)} */
- this.resolveLoadFinished_ = resolve;
- }.bind(this));
+ this.resetLoadFinished();
chrome.developerPrivate.onItemStateChanged.addListener(
function(eventData) {
@@ -276,14 +184,14 @@ cr.define('extensions', function() {
case EventType.ERRORS_REMOVED:
case EventType.PREFS_CHANGED:
if (eventData.extensionInfo) {
- this.updateExtension_(eventData.extensionInfo);
+ this.updateOrCreateWrapper_(eventData.extensionInfo);
this.focusGrid_.ensureRowActive();
}
break;
case EventType.UNINSTALLED:
var index = this.getIndexOfExtension_(eventData.item_id);
this.extensions_.splice(index, 1);
- this.removeNode_(getRequiredElement(eventData.item_id));
+ this.removeWrapper_(getRequiredElement(eventData.item_id));
break;
default:
assertNotReached();
@@ -312,6 +220,22 @@ cr.define('extensions', function() {
},
/**
+ * Resets the |loadFinished| promise so that it can be used again; this
+ * is useful if the page updates and tests need to wait for it to finish.
+ */
+ resetLoadFinished: function() {
+ /**
+ * |loadFinished| should be used for testing purposes and will be
+ * fulfilled when this list has finished loading the first time.
+ * @type {Promise}
+ * */
+ this.loadFinished = new Promise(function(resolve, reject) {
+ /** @private {function(?)} */
+ this.resolveLoadFinished_ = resolve;
+ }.bind(this));
+ },
+
+ /**
* Updates the extensions on the page.
* @param {boolean} incognitoAvailable Whether or not incognito is allowed.
* @param {boolean} enableAppInfoDialog Whether or not the app info dialog
@@ -358,19 +282,27 @@ cr.define('extensions', function() {
* @private
*/
onUpdateFinished_: function() {
- // Cannot focus or highlight a extension if there are none.
- if (this.extensions_.length == 0)
+ // Cannot focus or highlight a extension if there are none, and we should
+ // only scroll to a particular extension or open the options page once.
+ if (this.extensions_.length == 0 || this.didInitialNavigation_)
return;
+ this.didInitialNavigation_ = true;
assert(!this.hidden);
assert(!this.parentElement.hidden);
- this.updateFocusableElements();
-
var idToHighlight = this.getIdQueryParam_();
- if (idToHighlight && $(idToHighlight)) {
- this.scrollToNode_(idToHighlight);
- this.setInitialFocus_(idToHighlight);
+ if (idToHighlight) {
+ var wrapper = $(idToHighlight);
+ if (wrapper) {
+ this.scrollToWrapper_(idToHighlight);
+
+ var focusRow = wrapper.getFocusRow();
+ (focusRow.getFirstFocusable('enabled') ||
+ focusRow.getFirstFocusable('remove-enterprise') ||
+ focusRow.getFirstFocusable('website') ||
+ focusRow.getFirstFocusable('details')).focus();
+ }
}
var idToOpenOptions = this.getOptionsQueryParam_();
@@ -415,48 +347,46 @@ cr.define('extensions', function() {
// Iterate over the extension data and add each item to the list.
this.extensions_.forEach(function(extension) {
seenIds.push(extension.id);
- this.updateExtension_(extension);
+ this.updateOrCreateWrapper_(extension);
}, this);
this.focusGrid_.ensureRowActive();
// Remove extensions that are no longer installed.
- var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]');
- Array.prototype.forEach.call(nodes, function(node) {
- if (seenIds.indexOf(node.id) < 0)
- this.removeNode_(node);
+ var wrappers = document.querySelectorAll(
+ '.extension-list-item-wrapper[id]');
+ Array.prototype.forEach.call(wrappers, function(wrapper) {
+ if (seenIds.indexOf(wrapper.id) < 0)
+ this.removeWrapper_(wrapper);
}, this);
},
- /** 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();
- }
- },
-
/**
- * Removes the node from the DOM, and updates the focused element if needed.
- * @param {!HTMLElement} node
+ * Removes the wrapper from the DOM and updates the focused element if
+ * needed.
+ * @param {!Element} wrapper
* @private
*/
- removeNode_: function(node) {
- if (node.contains(document.activeElement)) {
- var nodes =
- document.querySelectorAll('.extension-list-item-wrapper[id]');
- var index = Array.prototype.indexOf.call(nodes, node);
+ removeWrapper_: function(wrapper) {
+ // If focus is in the wrapper about to be removed, move it first. This
+ // happens when clicking the trash can to remove an extension.
+ if (wrapper.contains(document.activeElement)) {
+ var wrappers = document.querySelectorAll(
+ '.extension-list-item-wrapper[id]');
+ var index = Array.prototype.indexOf.call(wrappers, wrapper);
assert(index != -1);
- var focusableNode = nodes[index + 1] || nodes[index - 1];
- if (focusableNode)
- focusableNode.getEquivalentElement(document.activeElement).focus();
+ var focusableWrapper = wrappers[index + 1] || wrappers[index - 1];
+ if (focusableWrapper) {
+ var newFocusRow = focusableWrapper.getFocusRow();
+ newFocusRow.getEquivalentElement(document.activeElement).focus();
+ }
}
- node.parentNode.removeChild(node);
- this.focusGrid_.removeRow(assertInstanceof(node, ExtensionFocusRow));
-
- // Unregister the removed node from events.
- assertInstanceof(node, ExtensionFocusRow).destroy();
+ var focusRow = wrapper.getFocusRow();
+ this.focusGrid_.removeRow(focusRow);
this.focusGrid_.ensureRowActive();
+ focusRow.destroy();
+
+ wrapper.parentNode.removeChild(wrapper);
},
/**
@@ -464,60 +394,95 @@ cr.define('extensions', function() {
* @param {string} extensionId The id of the extension to scroll to.
* @private
*/
- scrollToNode_: function(extensionId) {
+ scrollToWrapper_: function(extensionId) {
// Scroll offset should be calculated slightly higher than the actual
// offset of the element being scrolled to, so that it ends up not all
// the way at the top. That way it is clear that there are more elements
// above the element being scrolled to.
+ var wrapper = $(extensionId);
var scrollFudge = 1.2;
- var scrollTop = $(extensionId).offsetTop - scrollFudge *
- $(extensionId).clientHeight;
+ var scrollTop = wrapper.offsetTop - scrollFudge * wrapper.clientHeight;
setScrollTopForDocument(document, scrollTop);
},
/**
- * @param {string} extensionId The id of the extension that should have
- * initial focus
- * @private
- */
- setInitialFocus_: function(extensionId) {
- var focusRow = assertInstanceof($(extensionId), ExtensionFocusRow);
- var columnTypePriority = ['enabled', 'enterprise', 'website', 'details'];
- var elementToFocus = null;
- var elementPriority = columnTypePriority.length;
-
- for (var i = 0; i < focusRow.focusableElements.length; ++i) {
- var element = focusRow.focusableElements[i];
- var priority =
- columnTypePriority.indexOf(element.getAttribute('column-type'));
- if (priority > -1 && priority < elementPriority) {
- elementToFocus = element;
- elementPriority = priority;
- }
- }
-
- focusRow.getEquivalentElement(elementToFocus).focus();
- },
-
- /**
* Synthesizes and initializes an HTML element for the extension metadata
* given in |extension|.
* @param {!ExtensionInfo} 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.
+ * @param {?Element} nextWrapper The newly created wrapper will be inserted
+ * before |nextWrapper| if non-null (else it will be appended to the
+ * wrapper list).
* @private
*/
- createNode_: function(extension, nextNode) {
- var template = $('template-collection').querySelector(
- '.extension-list-item-wrapper');
- var node = template.cloneNode(true);
- ExtensionFocusRow.decorate(node, $('extension-settings-list'));
+ createWrapper_: function(extension, nextWrapper) {
+ var wrapper = new ExtensionWrapper;
+ wrapper.id = extension.id;
+
+ // The 'Permissions' link.
+ wrapper.setupColumn('details', '.permissions-link', 'click', function(e) {
+ if (!this.permissionsPromptIsShowing_) {
+ chrome.developerPrivate.showPermissionsDialog(extension.id,
+ function() {
+ this.permissionsPromptIsShowing_ = false;
+ }.bind(this));
+ this.permissionsPromptIsShowing_ = true;
+ }
+ e.preventDefault();
+ });
+
+ wrapper.setupColumn('options', '.options-button', 'click', function(e) {
+ this.showEmbeddedExtensionOptions_(extension.id, false);
+ e.preventDefault();
+ }.bind(this));
+
+ // The 'Options' button or link, depending on its behaviour.
+ // Set an href to get the correct mouse-over appearance (link,
+ // footer) - but the actual link opening is done through developerPrivate
+ // API with a preventDefault().
+ wrapper.querySelector('.options-link').href =
+ extension.optionsPage ? extension.optionsPage.url : '';
+ wrapper.setupColumn('options', '.options-link', 'click', function(e) {
+ chrome.developerPrivate.showOptions(extension.id);
+ e.preventDefault();
+ });
+
+ // The 'View in Web Store/View Web Site' link.
+ wrapper.setupColumn('website', '.site-link');
+
+ // The 'Launch' link.
+ wrapper.setupColumn('launch', '.launch-link', 'click', function(e) {
+ chrome.management.launchApp(extension.id);
+ });
- var row = assertInstanceof(node, ExtensionFocusRow);
- row.id = extension.id;
+ // The 'Reload' link.
+ wrapper.setupColumn('localReload', '.reload-link', 'click', function(e) {
+ chrome.developerPrivate.reload(extension.id, {failQuietly: true});
+ });
+
+ wrapper.setupColumn('errors', '.errors-link', 'click', function(e) {
+ var extensionId = extension.id;
+ assert(this.extensions_.length > 0);
+ var newEx = this.extensions_.filter(function(e) {
+ return e.state == chrome.developerPrivate.ExtensionState.ENABLED &&
+ e.id == extensionId;
+ })[0];
+ var errors = newEx.manifestErrors.concat(newEx.runtimeErrors);
+ extensions.ExtensionErrorOverlay.getInstance().setErrorsAndShowOverlay(
+ errors, extensionId, newEx.name);
+ }.bind(this));
+
+ wrapper.setupColumn('suspiciousLearnMore',
+ '.suspicious-install-message .learn-more-link');
+
+ // The path, if provided by unpacked extension.
+ wrapper.setupColumn('loadPath', '.load-path a:first-of-type', 'click',
+ function(e) {
+ chrome.developerPrivate.showPath(extension.id);
+ e.preventDefault();
+ });
// The 'Show Browser Action' button.
- row.setupColumn('.show-button', 'showButton', 'click', function(e) {
+ wrapper.setupColumn('showButton', '.show-button', 'click', function(e) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: extension.id,
showActionButton: true
@@ -525,18 +490,12 @@ cr.define('extensions', function() {
});
// The 'allow in incognito' checkbox.
- row.setupColumn('.incognito-control input', 'incognito', 'change',
- function(e) {
- var butterBar = row.querySelector('.butter-bar');
+ wrapper.setupColumn('incognito', '.incognito-control input', 'change',
+ function(e) {
+ var butterBar = wrapper.querySelector('.butter-bar');
var checked = e.target.checked;
- if (!this.butterbarShown_) {
- butterBar.hidden = !checked ||
- extension.type ==
- chrome.developerPrivate.ExtensionType.HOSTED_APP;
- this.butterbarShown_ = !butterBar.hidden;
- } else {
- butterBar.hidden = true;
- }
+ butterBar.hidden = !checked ||
+ extension.type == ExtensionType.HOSTED_APP;
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: extension.id,
incognitoAccess: e.target.checked
@@ -546,8 +505,8 @@ cr.define('extensions', 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.
- row.setupColumn('.error-collection-control input', 'dev-collectErrors',
- 'change', function(e) {
+ wrapper.setupColumn('collectErrors', '.error-collection-control input',
+ 'change', function(e) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: extension.id,
errorCollection: e.target.checked
@@ -557,8 +516,8 @@ cr.define('extensions', 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.
- row.setupColumn('.all-urls-control input', 'allUrls', 'click',
- function(e) {
+ wrapper.setupColumn('allUrls', '.all-urls-control input', 'click',
+ function(e) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: extension.id,
runOnAllUrls: e.target.checked
@@ -566,82 +525,29 @@ cr.define('extensions', function() {
});
// The 'allow file:// access' checkbox.
- row.setupColumn('.file-access-control input', 'localUrls', 'click',
- function(e) {
+ wrapper.setupColumn('localUrls', '.file-access-control input', 'click',
+ function(e) {
chrome.developerPrivate.updateExtensionConfiguration({
extensionId: extension.id,
fileAccess: e.target.checked
});
});
- // The 'Options' button or link, depending on its behaviour.
- // Set an href to get the correct mouse-over appearance (link,
- // footer) - but the actual link opening is done through developerPrivate
- // API with a preventDefault().
- row.querySelector('.options-link').href =
- extension.optionsPage ? extension.optionsPage.url : '';
- row.setupColumn('.options-link', 'options', 'click', function(e) {
- chrome.developerPrivate.showOptions(extension.id);
- e.preventDefault();
- });
-
- 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.
- row.setupColumn('.permissions-link', 'details', 'click', function(e) {
- if (!this.permissionsPromptIsShowing_) {
- chrome.developerPrivate.showPermissionsDialog(extension.id,
- function() {
- this.permissionsPromptIsShowing_ = false;
- }.bind(this));
- this.permissionsPromptIsShowing_ = true;
- }
- e.preventDefault();
- });
-
- // The 'Reload' link.
- row.setupColumn('.reload-link', 'localReload', 'click', function(e) {
- chrome.developerPrivate.reload(extension.id, {failQuietly: true});
- });
-
- // The 'Launch' link.
- row.setupColumn('.launch-link', 'launch', 'click', function(e) {
- chrome.management.launchApp(extension.id);
- });
-
- row.setupColumn('.errors-link', 'errors', 'click', function(e) {
- var extensionId = extension.id;
- assert(this.extensions_.length > 0);
- var newEx = this.extensions_.filter(function(e) {
- return e.state == chrome.developerPrivate.ExtensionState.ENABLED &&
- e.id == extensionId;
- })[0];
- var errors = newEx.manifestErrors.concat(newEx.runtimeErrors);
- extensions.ExtensionErrorOverlay.getInstance().setErrorsAndShowOverlay(
- errors, extensionId, newEx.name);
- }.bind(this));
-
// The 'Reload' terminated link.
- row.setupColumn('.terminated-reload-link', 'terminatedReload', 'click',
- function(e) {
+ wrapper.setupColumn('terminatedReload', '.terminated-reload-link',
+ 'click', function(e) {
chrome.developerPrivate.reload(extension.id, {failQuietly: true});
});
// The 'Repair' corrupted link.
- row.setupColumn('.corrupted-repair-button', 'repair', 'click',
- function(e) {
+ wrapper.setupColumn('repair', '.corrupted-repair-button', 'click',
+ function(e) {
chrome.developerPrivate.repairExtension(extension.id);
});
// The 'Enabled' checkbox.
- row.setupColumn('.enable-checkbox input', 'enabled', 'change',
- function(e) {
+ wrapper.setupColumn('enabled', '.enable-checkbox input', 'change',
+ function(e) {
var checked = e.target.checked;
// TODO(devlin): What should we do if this fails?
chrome.management.setEnabled(extension.id, checked);
@@ -655,11 +561,12 @@ cr.define('extensions', function() {
});
// 'Remove' button.
- var trashTemplate = $('template-collection').querySelector('.trash');
- var trash = trashTemplate.cloneNode(true);
+ var trash = cloneTemplate('trash');
trash.title = loadTimeData.getString('extensionUninstall');
- trash.setAttribute('column-type', 'trash');
- trash.addEventListener('click', function(e) {
+
+ wrapper.querySelector('.enable-controls').appendChild(trash);
+
+ wrapper.setupColumn('remove-enterprise', '.trash', 'click', function(e) {
trash.classList.add('open');
trash.classList.toggle('mouse-clicked', e.detail > 0);
if (this.uninstallIsShowing_)
@@ -683,86 +590,68 @@ cr.define('extensions', function() {
}
}.bind(this));
}.bind(this));
- row.querySelector('.enable-controls').appendChild(trash);
-
- // Developer mode ////////////////////////////////////////////////////////
-
- // The path, if provided by unpacked extension.
- row.setupColumn('.load-path a:first-of-type', 'dev-loadPath', 'click',
- function(e) {
- chrome.developerPrivate.showPath(extension.id);
- e.preventDefault();
- });
// Maintain the order that nodes should be in when creating as well as
- // when adding only one new row.
- this.insertBefore(row, nextNode);
- this.updateNode_(extension, row);
+ // when adding only one new wrapper.
+ this.insertBefore(wrapper, nextWrapper);
+ this.updateWrapper_(extension, wrapper);
- var nextRow = null;
- if (nextNode)
- nextRow = assertInstanceof(nextNode, ExtensionFocusRow);
-
- this.focusGrid_.addRowBefore(row, nextRow);
+ var nextRow = this.focusGrid_.getRowForRoot(nextWrapper); // May be null.
+ this.focusGrid_.addRowBefore(wrapper.getFocusRow(), nextRow);
},
/**
* Updates an HTML element for the extension metadata given in |extension|.
* @param {!ExtensionInfo} extension A dictionary of extension metadata.
- * @param {!ExtensionFocusRow} row The node that is being updated.
+ * @param {!Element} wrapper The extension wrapper element to update.
* @private
*/
- updateNode_: function(extension, row) {
+ updateWrapper_: function(extension, wrapper) {
var isActive =
extension.state == chrome.developerPrivate.ExtensionState.ENABLED;
- row.classList.toggle('inactive-extension', !isActive);
+ wrapper.classList.toggle('inactive-extension', !isActive);
+ wrapper.classList.remove('controlled', 'may-not-remove');
- // 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('controlled', 'may-not-remove');
- var classes = [];
if (extension.controlledInfo) {
- classes.push('controlled');
+ wrapper.classList.add('controlled');
} else if (!extension.userMayModify ||
extension.mustRemainInstalled ||
extension.dependentExtensions.length > 0) {
- classes.push('may-not-remove');
+ wrapper.classList.add('may-not-remove');
}
- row.classList.add.apply(row.classList, classes);
- var item = row.querySelector('.extension-list-item');
+ var item = wrapper.querySelector('.extension-list-item');
item.style.backgroundImage = 'url(' + extension.iconUrl + ')';
- 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);
+ this.setText_(wrapper, '.extension-title', extension.name);
+ this.setText_(wrapper, '.extension-version', extension.version);
+ this.setText_(wrapper, '.location-text', extension.locationText || '');
+ this.setText_(wrapper, '.blacklist-text', extension.blacklistText || '');
+ this.setText_(wrapper, '.extension-description', extension.description);
// The 'Show Browser Action' button.
- this.updateVisibility_(row, '.show-button',
+ this.updateVisibility_(wrapper, '.show-button',
isActive && extension.actionButtonHidden);
// The 'allow in incognito' checkbox.
- this.updateVisibility_(row, '.incognito-control',
+ this.updateVisibility_(wrapper, '.incognito-control',
isActive && this.incognitoAvailable_,
function(item) {
var incognito = item.querySelector('input');
incognito.disabled = !extension.incognitoAccess.isEnabled;
incognito.checked = extension.incognitoAccess.isActive;
});
-
- // Hide butterBar if incognito is not enabled for the extension.
- var butterBar = row.querySelector('.butter-bar');
- butterBar.hidden =
- butterBar.hidden || !extension.incognitoAccess.isEnabled;
+ var showButterBar = isActive &&
+ extension.incognitoAccess.isActive &&
+ extension.type != ExtensionType.HOSTED_APP;
+ // The 'allow in incognito' butter bar.
+ this.updateVisibility_(wrapper, '.butter-bar', showButterBar);
// 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_(
- row, '.error-collection-control',
+ wrapper, '.error-collection-control',
isActive && extension.errorCollection.isEnabled,
function(item) {
item.querySelector('input').checked =
@@ -773,14 +662,14 @@ cr.define('extensions', function() {
// active script restrictions are enabled. If they are not enabled, no
// extensions should want all urls.
this.updateVisibility_(
- row, '.all-urls-control',
+ wrapper, '.all-urls-control',
isActive && extension.runOnAllUrls.isEnabled,
function(item) {
item.querySelector('input').checked = extension.runOnAllUrls.isActive;
});
// The 'allow file:// access' checkbox.
- this.updateVisibility_(row, '.file-access-control',
+ this.updateVisibility_(wrapper, '.file-access-control',
isActive && extension.fileAccess.isEnabled,
function(item) {
item.querySelector('input').checked = extension.fileAccess.isActive;
@@ -788,15 +677,15 @@ cr.define('extensions', function() {
// The 'Options' button or link, depending on its behaviour.
var optionsEnabled = isActive && !!extension.optionsPage;
- this.updateVisibility_(row, '.options-link', optionsEnabled &&
+ this.updateVisibility_(wrapper, '.options-link', optionsEnabled &&
extension.optionsPage.openInTab);
- this.updateVisibility_(row, '.options-button', optionsEnabled &&
+ this.updateVisibility_(wrapper, '.options-button', optionsEnabled &&
!extension.optionsPage.openInTab);
// The 'View in Web Store/View Web Site' link.
var siteLinkEnabled = !!extension.homePage.url &&
!this.enableAppInfoDialog_;
- this.updateVisibility_(row, '.site-link', siteLinkEnabled,
+ this.updateVisibility_(wrapper, '.site-link', siteLinkEnabled,
function(item) {
item.href = extension.homePage.url;
item.textContent = loadTimeData.getString(
@@ -807,18 +696,19 @@ cr.define('extensions', function() {
var isUnpacked =
extension.location == chrome.developerPrivate.Location.UNPACKED;
// The 'Reload' link.
- this.updateVisibility_(row, '.reload-link', isUnpacked);
+ this.updateVisibility_(wrapper, '.reload-link', isUnpacked);
// The 'Launch' link.
this.updateVisibility_(
- row, '.launch-link',
- isUnpacked && extension.type ==
- chrome.developerPrivate.ExtensionType.PLATFORM_APP);
+ wrapper, '.launch-link',
+ isUnpacked && extension.type == ExtensionType.PLATFORM_APP &&
+ isActive);
// The 'Errors' link.
var hasErrors = extension.runtimeErrors.length > 0 ||
extension.manifestErrors.length > 0;
- this.updateVisibility_(row, '.errors-link', hasErrors, function(item) {
+ this.updateVisibility_(wrapper, '.errors-link', hasErrors,
+ function(item) {
var Level = chrome.developerPrivate.ErrorLevel;
var map = {};
@@ -846,18 +736,18 @@ cr.define('extensions', function() {
// The 'Reload' terminated link.
var isTerminated =
extension.state == chrome.developerPrivate.ExtensionState.TERMINATED;
- this.updateVisibility_(row, '.terminated-reload-link', isTerminated);
+ this.updateVisibility_(wrapper, '.terminated-reload-link', isTerminated);
// The 'Repair' corrupted link.
var canRepair = !isTerminated &&
extension.disableReasons.corruptInstall &&
extension.location ==
chrome.developerPrivate.Location.FROM_STORE;
- this.updateVisibility_(row, '.corrupted-repair-button', canRepair);
+ this.updateVisibility_(wrapper, '.corrupted-repair-button', canRepair);
// The 'Enabled' checkbox.
var isOK = !isTerminated && !canRepair;
- this.updateVisibility_(row, '.enable-checkbox', isOK, function(item) {
+ this.updateVisibility_(wrapper, '.enable-checkbox', isOK, function(item) {
var enableCheckboxDisabled =
!extension.userMayModify ||
extension.disableReasons.suspiciousInstall ||
@@ -869,7 +759,7 @@ cr.define('extensions', function() {
});
// Indicator for extensions controlled by policy.
- var controlNode = row.querySelector('.enable-controls');
+ var controlNode = wrapper.querySelector('.enable-controls');
var indicator =
controlNode.querySelector('.controlled-extension-indicator');
var needsIndicator = isOK && extension.controlledInfo;
@@ -895,8 +785,7 @@ cr.define('extensions', function() {
indicator.setAttribute('text' + controlledByStr, text);
indicator.image.setAttribute('aria-label', text);
controlNode.appendChild(indicator);
- indicator.querySelector('div').setAttribute('column-type',
- 'enterprise');
+ wrapper.setupColumn('remove-enterprise', '[controlled-by] div');
} else if (!needsIndicator && indicator) {
controlNode.removeChild(indicator);
}
@@ -904,11 +793,11 @@ cr.define('extensions', function() {
// Developer mode ////////////////////////////////////////////////////////
// First we have the id.
- var idLabel = row.querySelector('.extension-id');
+ var idLabel = wrapper.querySelector('.extension-id');
idLabel.textContent = ' ' + extension.id;
// Then the path, if provided by unpacked extension.
- this.updateVisibility_(row, '.load-path', isUnpacked,
+ this.updateVisibility_(wrapper, '.load-path', isUnpacked,
function(item) {
item.querySelector('a:first-of-type').textContent =
' ' + extension.prettifiedPath;
@@ -919,32 +808,31 @@ cr.define('extensions', function() {
// extension is disabled.
var isRequired =
!extension.userMayModify || extension.mustRemainInstalled;
- this.updateVisibility_(row, '.managed-message', isRequired &&
+ this.updateVisibility_(wrapper, '.managed-message', isRequired &&
!extension.disableReasons.updateRequired);
// Then the 'This isn't from the webstore, looks suspicious' message.
- this.updateVisibility_(row, '.suspicious-install-message', !isRequired &&
- extension.disableReasons.suspiciousInstall);
+ var isSuspicious = extension.disableReasons.suspiciousInstall;
+ this.updateVisibility_(wrapper, '.suspicious-install-message',
+ !isRequired && isSuspicious);
// Then the 'This is a corrupt extension' message.
- this.updateVisibility_(row, '.corrupt-install-message', !isRequired &&
+ this.updateVisibility_(wrapper, '.corrupt-install-message', !isRequired &&
extension.disableReasons.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_(row, '.update-required-message',
+ this.updateVisibility_(wrapper, '.update-required-message',
extension.disableReasons.updateRequired);
// The 'following extensions depend on this extension' list.
var hasDependents = extension.dependentExtensions.length > 0;
- row.classList.toggle('developer-extras', hasDependents);
- this.updateVisibility_(row, '.dependent-extensions-message',
+ wrapper.classList.toggle('developer-extras', hasDependents);
+ this.updateVisibility_(wrapper, '.dependent-extensions-message',
hasDependents, function(item) {
var dependentList = item.querySelector('ul');
dependentList.textContent = '';
- var dependentTemplate = $('template-collection').querySelector(
- '.dependent-list-item');
extension.dependentExtensions.forEach(function(dependentId) {
var dependentExtension = null;
for (var i = 0; i < this.extensions_.length; ++i) {
@@ -956,7 +844,7 @@ cr.define('extensions', function() {
if (!dependentExtension)
return;
- var depNode = dependentTemplate.cloneNode(true);
+ var depNode = cloneTemplate('dependent-list-item');
depNode.querySelector('.dep-extension-title').textContent =
dependentExtension.name;
depNode.querySelector('.dep-extension-id').textContent =
@@ -966,8 +854,8 @@ cr.define('extensions', function() {
}.bind(this));
// The active views.
- this.updateVisibility_(row, '.active-views', extension.views.length > 0,
- function(item) {
+ this.updateVisibility_(wrapper, '.active-views',
+ extension.views.length > 0, function(item) {
var link = item.querySelector('a');
// Link needs to be an only child before the list is updated.
@@ -997,7 +885,9 @@ cr.define('extensions', function() {
(view.incognito ?
' ' + loadTimeData.getString('viewIncognito') : '') +
(view.renderProcessId == -1 ?
- ' ' + loadTimeData.getString('viewInactive') : '');
+ ' ' + loadTimeData.getString('viewInactive') : '') +
+ (view.isIframe ?
+ ' ' + loadTimeData.getString('viewIframe') : '');
link.textContent = label;
link.clickHandler = function(e) {
chrome.developerPrivate.openDevTools({
@@ -1013,16 +903,13 @@ cr.define('extensions', function() {
link = link.cloneNode(true);
item.appendChild(link);
}
- });
- var allLinks = item.querySelectorAll('a');
- for (var i = 0; i < allLinks.length; ++i) {
- allLinks[i].setAttribute('column-type', 'dev-activeViews' + i);
- }
+ wrapper.setupColumn('activeView', '.active-views a:last-of-type');
+ });
});
// The extension warnings (describing runtime issues).
- this.updateVisibility_(row, '.extension-warnings',
+ this.updateVisibility_(wrapper, '.extension-warnings',
extension.runtimeWarnings.length > 0,
function(item) {
var warningList = item.querySelector('ul');
@@ -1034,7 +921,7 @@ cr.define('extensions', function() {
});
// Install warnings.
- this.updateVisibility_(row, '.install-warnings',
+ this.updateVisibility_(wrapper, '.install-warnings',
extension.installWarnings.length > 0,
function(item) {
var installWarningList = item.querySelector('ul');
@@ -1051,19 +938,17 @@ cr.define('extensions', function() {
if (location.hash.substr(1) == extension.id) {
// Scroll beneath the fixed header so that the extension is not
// obscured.
- var topScroll = row.offsetTop - $('page-header').offsetHeight;
- var pad = parseInt(window.getComputedStyle(row, null).marginTop, 10);
+ var topScroll = wrapper.offsetTop - $('page-header').offsetHeight;
+ var pad = parseInt(window.getComputedStyle(wrapper).marginTop, 10);
if (!isNaN(pad))
topScroll -= pad / 2;
setScrollTopForDocument(document, topScroll);
}
-
- row.updateFocusableElements();
},
/**
* Updates an element's textContent.
- * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {Node} node Ancestor of the element specified by |query|.
* @param {string} query A query to select an element in |node|.
* @param {string} textContent
* @private
@@ -1075,7 +960,7 @@ cr.define('extensions', function() {
/**
* Updates an element's visibility and calls |shownCallback| if it is
* visible.
- * @param {Element} node Ancestor of the element specified by |query|.
+ * @param {Node} 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)=} opt_shownCallback Callback if the element is
@@ -1084,10 +969,10 @@ cr.define('extensions', function() {
* @private
*/
updateVisibility_: function(node, query, visible, opt_shownCallback) {
- var item = assert(node.querySelector(query));
- item.hidden = !visible;
+ var element = assertInstanceof(node.querySelector(query), Element);
+ element.hidden = !visible;
if (visible && opt_shownCallback)
- opt_shownCallback(item);
+ opt_shownCallback(element);
},
/**
@@ -1112,7 +997,7 @@ cr.define('extensions', function() {
return;
if (scroll)
- this.scrollToNode_(extensionId);
+ this.scrollToWrapper_(extensionId);
// Add the options query string. Corner case: the 'options' query string
// will clobber the 'id' query string if the options link is clicked when
@@ -1161,12 +1046,12 @@ cr.define('extensions', function() {
},
/**
- * Updates the node for the extension.
+ * Updates or creates a wrapper for |extension|.
* @param {!ExtensionInfo} extension The information about the extension to
* update.
* @private
*/
- updateExtension_: function(extension) {
+ updateOrCreateWrapper_: function(extension) {
var currIndex = this.getIndexOfExtension_(extension.id);
if (currIndex != -1) {
// If there is a current version of the extension, update it with the
@@ -1180,12 +1065,12 @@ cr.define('extensions', function() {
this.extensions_.sort(compareExtensions);
}
- var node = /** @type {ExtensionFocusRow} */ ($(extension.id));
- if (node) {
- this.updateNode_(extension, node);
+ var wrapper = $(extension.id);
+ if (wrapper) {
+ this.updateWrapper_(extension, wrapper);
} else {
var nextExt = this.extensions_[this.extensions_.indexOf(extension) + 1];
- this.createNode_(extension, nextExt ? $(nextExt.id) : null);
+ this.createWrapper_(extension, nextExt ? $(nextExt.id) : null);
}
}
};
diff --git a/chromium/chrome/browser/resources/extensions/extensions.css b/chromium/chrome/browser/resources/extensions/extensions.css
index c94dfad6d5a..011d0f7d3d8 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.css
+++ b/chromium/chrome/browser/resources/extensions/extensions.css
@@ -180,6 +180,7 @@ html[dir='rtl'] .extension-list-item {
.extension-title {
-webkit-padding-end: 20px;
+ -webkit-user-select: text;
color: rgb(48, 57, 66);
display: inline;
font-size: 14px;
@@ -414,7 +415,7 @@ html[dir='rtl'] #extension-settings .trash {
}
.profile-is-supervised-banner .page-banner-text {
- background-image: url(chrome://theme/IDR_WARNING);
+ background-image: url(../../../../ui/webui/resources/images/warning.svg);
}
/* Sideload Wipeout */
diff --git a/chromium/chrome/browser/resources/extensions/extensions.html b/chromium/chrome/browser/resources/extensions/extensions.html
index 58bbf471d84..155714e11e5 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.html
+++ b/chromium/chrome/browser/resources/extensions/extensions.html
@@ -127,7 +127,7 @@
<span id="font-measuring-div"></span>
-<div id="template-collection" hidden>
+<div id="templates" hidden>
<div class="extension-list-item-wrapper">
<div class="extension-list-item">
diff --git a/chromium/chrome/browser/resources/extensions/extensions.js b/chromium/chrome/browser/resources/extensions/extensions.js
index 863d2fe6b60..e957033c9c8 100644
--- a/chromium/chrome/browser/resources/extensions/extensions.js
+++ b/chromium/chrome/browser/resources/extensions/extensions.js
@@ -9,6 +9,7 @@
<include src="extension_commands_overlay.js">
<include src="extension_error_overlay.js">
<include src="extension_focus_manager.js">
+<include src="focus_row.js">
<include src="extension_list.js">
<include src="pack_extension_overlay.js">
<include src="extension_loader.js">
@@ -125,6 +126,12 @@ cr.define('extensions', function() {
dragEnabled_: false,
/**
+ * True if the page has finished the initial load.
+ * @private {boolean}
+ */
+ hasLoaded_: false,
+
+ /**
* Perform initial setup.
*/
initialize: function() {
@@ -152,7 +159,6 @@ cr.define('extensions', function() {
$('toggle-dev-on').addEventListener('change', function(e) {
this.updateDevControlsVisibility_(true);
- extensionList.updateFocusableElements();
chrome.developerPrivate.updateProfileConfiguration(
{inDeveloperMode: e.target.checked});
var suffix = $('toggle-dev-on').checked ? 'Enabled' : 'Disabled';
@@ -241,7 +247,11 @@ cr.define('extensions', function() {
* @private
*/
update_: function(profileInfo) {
- this.setLoading_(true);
+ // We only set the page to be loading if we haven't already finished an
+ // initial load, because otherwise the updates are all incremental and
+ // don't need to display the interstitial spinner.
+ if (!this.hasLoaded_)
+ this.setLoading_(true);
webuiResponded = true;
/** @const */
@@ -260,7 +270,10 @@ cr.define('extensions', function() {
extensionList.updateExtensionsData(
profileInfo.isIncognitoAvailable,
profileInfo.appInfoDialogEnabled).then(function() {
- this.setLoading_(false);
+ if (!this.hasLoaded_) {
+ this.hasLoaded_ = true;
+ this.setLoading_(false);
+ }
this.onExtensionCountChanged();
}.bind(this));
},
@@ -392,13 +405,11 @@ cr.define('extensions', function() {
lastFocused = document.activeElement;
$('overlay').addEventListener('cancelOverlay', function f() {
- console.log('cancelOverlay');
- console.log('lastFocused', lastFocused);
- console.log('focusOutlineManager.visible', focusOutlineManager.visible);
if (lastFocused && focusOutlineManager.visible)
lastFocused.focus();
$('overlay').removeEventListener('cancelOverlay', f);
+ uber.replaceState({}, '');
});
node.classList.add('showing');
}
diff --git a/chromium/chrome/browser/resources/extensions/focus_row.js b/chromium/chrome/browser/resources/extensions/focus_row.js
new file mode 100644
index 00000000000..6e9b078f302
--- /dev/null
+++ b/chromium/chrome/browser/resources/extensions/focus_row.js
@@ -0,0 +1,30 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+cr.define('extensions', function() {
+ /**
+ * @param {!Element} root
+ * @param {Node} boundary
+ * @constructor
+ * @extends {cr.ui.FocusRow}
+ */
+ function FocusRow(root, boundary) {
+ cr.ui.FocusRow.call(this, root, boundary);
+ }
+
+ FocusRow.prototype = {
+ __proto__: cr.ui.FocusRow.prototype,
+
+ /** @override */
+ makeActive: function(active) {
+ cr.ui.FocusRow.prototype.makeActive.call(this, active);
+
+ // Only highlight if the row has focus.
+ this.root.classList.toggle('extension-highlight',
+ active && this.root.contains(document.activeElement));
+ },
+ };
+
+ return {FocusRow: FocusRow};
+});