| Index: chrome/browser/resources/extensions/extension_error.js
|
| diff --git a/chrome/browser/resources/extensions/extension_error.js b/chrome/browser/resources/extensions/extension_error.js
|
| index 7b9b7a1087619df0965c1fbc02ac1d7aec461a72..c9296554d9aea901150212419fc8e1befca30780 100644
|
| --- a/chrome/browser/resources/extensions/extension_error.js
|
| +++ b/chrome/browser/resources/extensions/extension_error.js
|
| @@ -26,6 +26,19 @@ cr.define('extensions', function() {
|
| }
|
|
|
| /**
|
| + * @param {!Array<(ManifestError|RuntimeError)>} errors
|
| + * @param {number} id
|
| + * @return {number} The index of the error with |id|, or -1 if not found.
|
| + */
|
| + function findErrorById(errors, id) {
|
| + for (var i = 0; i < errors.length; ++i) {
|
| + if (errors[i].id == id)
|
| + return i;
|
| + }
|
| + return -1;
|
| + }
|
| +
|
| + /**
|
| * Creates a new ExtensionError HTMLElement; this is used to show a
|
| * notification to the user when an error is caused by an extension.
|
| * @param {(RuntimeError|ManifestError)} error The error the element should
|
| @@ -46,7 +59,14 @@ cr.define('extensions', function() {
|
|
|
| /** @override */
|
| getEquivalentElement: function(element) {
|
| - return assert(this.querySelector('.extension-error-view-details'));
|
| + 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;
|
| },
|
|
|
| /**
|
| @@ -58,6 +78,12 @@ cr.define('extensions', function() {
|
| decorateWithError_: function(error, boundary) {
|
| this.decorate(boundary);
|
|
|
| + /**
|
| + * The backing error.
|
| + * @type {(ManifestError|RuntimeError)}
|
| + */
|
| + this.error = error;
|
| +
|
| // Add an additional class for the severity level.
|
| if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
|
| switch (error.severity) {
|
| @@ -87,28 +113,35 @@ cr.define('extensions', function() {
|
|
|
| var messageSpan = this.querySelector('.extension-error-message');
|
| messageSpan.textContent = error.message;
|
| - messageSpan.title = error.message;
|
|
|
| - var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
|
| - var viewDetailsLink = this.querySelector('.extension-error-view-details');
|
| + var deleteButton = this.querySelector('.error-delete-button');
|
| + deleteButton.addEventListener('click', function(e) {
|
| + this.dispatchEvent(
|
| + new CustomEvent('deleteExtensionError',
|
| + {bubbles: true, detail: this.error}));
|
| + }.bind(this));
|
|
|
| - // If we cannot open the file source and there are no external frames in
|
| - // the stack, then there are no details to display.
|
| - if (!extensions.ExtensionErrorOverlay.canShowOverlayForError(
|
| - error, extensionUrl)) {
|
| - viewDetailsLink.hidden = true;
|
| - } else {
|
| - var stringId = extensionUrl.toLowerCase() == 'manifest.json' ?
|
| - 'extensionErrorViewManifest' : 'extensionErrorViewDetails';
|
| - viewDetailsLink.textContent = loadTimeData.getString(stringId);
|
| + this.addEventListener('click', function(e) {
|
| + if (e.target != deleteButton)
|
| + this.requestActive_();
|
| + }.bind(this));
|
| + this.addEventListener('keydown', function(e) {
|
| + if (e.keyIdentifier == 'Enter' && e.target != deleteButton)
|
| + this.requestActive_();
|
| + });
|
|
|
| - viewDetailsLink.addEventListener('click', function(e) {
|
| - extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay(
|
| - error, extensionUrl);
|
| - });
|
| + this.addFocusableElement(this);
|
| + this.addFocusableElement(this.querySelector('.error-delete-button'));
|
| + },
|
|
|
| - this.addFocusableElement(viewDetailsLink);
|
| - }
|
| + /**
|
| + * Bubble up an event to request to become active.
|
| + * @private
|
| + */
|
| + requestActive_: function() {
|
| + this.dispatchEvent(
|
| + new CustomEvent('highlightExtensionError',
|
| + {bubbles: true, detail: this.error}));
|
| },
|
| };
|
|
|
| @@ -116,138 +149,233 @@ cr.define('extensions', function() {
|
| * A variable length list of runtime or manifest errors for a given extension.
|
| * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
|
| * errors with which to populate the list.
|
| + * @param {string} extensionId The id of the extension.
|
| * @constructor
|
| * @extends {HTMLDivElement}
|
| */
|
| - function ExtensionErrorList(errors) {
|
| + function ExtensionErrorList(errors, extensionId) {
|
| var div = cloneTemplate('extension-error-list');
|
| div.__proto__ = ExtensionErrorList.prototype;
|
| - div.errors_ = errors;
|
| - div.decorate();
|
| + div.extensionId_ = extensionId;
|
| + div.decorate(errors);
|
| return div;
|
| }
|
|
|
| - /**
|
| - * @private
|
| - * @const
|
| - * @type {number}
|
| - */
|
| - ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
|
| -
|
| ExtensionErrorList.prototype = {
|
| __proto__: HTMLDivElement.prototype,
|
|
|
| - decorate: function() {
|
| + /**
|
| + * Initializes the extension error list.
|
| + * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
|
| + */
|
| + decorate: function(errors) {
|
| + /**
|
| + * @private {!Array<(ManifestError|RuntimeError)>}
|
| + */
|
| + this.errors_ = [];
|
| +
|
| 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));
|
| - this.errors_.forEach(function(error) {
|
| - if (idIsValid(error.extensionId)) {
|
| - var focusRow = new ExtensionError(error, this.gridBoundary_);
|
| - this.gridBoundary_.appendChild(
|
| - document.createElement('li')).appendChild(focusRow);
|
| - this.focusGrid_.addRow(focusRow);
|
| + errors.forEach(this.addError_, this);
|
| +
|
| + this.addEventListener('highlightExtensionError', function(e) {
|
| + this.setActiveErrorNode_(e.target);
|
| + });
|
| + this.addEventListener('deleteExtensionError', function(e) {
|
| + this.removeError_(e.detail);
|
| + });
|
| +
|
| + this.querySelector('#extension-error-list-clear').addEventListener(
|
| + 'click', function(e) {
|
| + this.clear(true);
|
| + }.bind(this));
|
| +
|
| + /**
|
| + * The callback for the extension changed event.
|
| + * @private {function(EventData):void}
|
| + */
|
| + this.onItemStateChangedListener_ = function(data) {
|
| + var type = chrome.developerPrivate.EventType;
|
| + if ((data.event_type == type.ERRORS_REMOVED ||
|
| + data.event_type == type.ERROR_ADDED) &&
|
| + data.extensionInfo.id == this.extensionId_) {
|
| + var newErrors = data.extensionInfo.runtimeErrors.concat(
|
| + data.extensionInfo.manifestErrors);
|
| + this.updateErrors_(newErrors);
|
| }
|
| - }, this);
|
| - this.focusGrid_.ensureRowActive();
|
| + }.bind(this);
|
| +
|
| + chrome.developerPrivate.onItemStateChanged.addListener(
|
| + this.onItemStateChangedListener_);
|
|
|
| - var numShowing = this.focusGrid_.rows.length;
|
| - if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_)
|
| - this.initShowMoreLink_();
|
| + /**
|
| + * The active error element in the list.
|
| + * @private {?}
|
| + */
|
| + this.activeError_ = null;
|
| +
|
| + this.setActiveError(0);
|
| },
|
|
|
| /**
|
| - * @return {?Element} The element that toggles between show more and show
|
| - * less, or null if it's hidden. Button will be hidden if there are less
|
| - * errors than |MAX_ERRORS_TO_SHOW_|.
|
| + * Adds an error to the list.
|
| + * @param {(RuntimeError|ManifestError)} error The error to add.
|
| + * @private
|
| */
|
| - getToggleElement: function() {
|
| - return this.querySelector(
|
| - '.extension-error-list-show-more [is="action-link"]:not([hidden])');
|
| + 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);
|
| },
|
|
|
| - /** @return {!Element} The element containing the list of errors. */
|
| - getErrorListElement: function() {
|
| - return this.gridBoundary_;
|
| + /**
|
| + * Removes an error from the list.
|
| + * @param {(RuntimeError|ManifestError)} error The error to remove.
|
| + * @private
|
| + */
|
| + removeError_: function(error) {
|
| + var index = 0;
|
| + for (; index < this.errors_.length; ++index) {
|
| + if (this.errors_[index].id == error.id)
|
| + break;
|
| + }
|
| + assert(index != this.errors_.length);
|
| + var errorList = this.querySelector('.extension-error-list-contents');
|
| +
|
| + var wasActive =
|
| + this.activeError_ && this.activeError_.error.id == error.id;
|
| +
|
| + this.errors_.splice(index, 1);
|
| + var listElement = errorList.children[index];
|
| + listElement.parentNode.removeChild(listElement);
|
| +
|
| + if (wasActive) {
|
| + index = Math.min(index, this.errors_.length - 1);
|
| + this.setActiveError(index); // Gracefully handles the -1 case.
|
| + }
|
| +
|
| + chrome.developerPrivate.deleteExtensionErrors({
|
| + extensionId: error.extensionId,
|
| + errorIds: [error.id]
|
| + });
|
| +
|
| + if (this.errors_.length == 0)
|
| + this.querySelector('#no-errors-span').hidden = false;
|
| },
|
|
|
| /**
|
| - * 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.
|
| + * Updates the list of errors.
|
| + * @param {!Array<(ManifestError|RuntimeError)>} newErrors The new list of
|
| + * errors.
|
| * @private
|
| */
|
| - onFocusin_: function() {
|
| - this.gridBoundary_.tabIndex = -1;
|
| + updateErrors_: function(newErrors) {
|
| + this.errors_.forEach(function(error) {
|
| + if (findErrorById(newErrors, error.id) == -1)
|
| + this.removeError_(error);
|
| + }, this);
|
| + newErrors.forEach(function(error) {
|
| + var index = findErrorById(this.errors_, error.id);
|
| + if (index == -1)
|
| + this.addError_(error);
|
| + else
|
| + this.errors_[index] = error; // Update the existing reference.
|
| + }, this);
|
| },
|
|
|
| /**
|
| - * Focus the first focusable row when tabbing into the grid for the
|
| - * first time.
|
| - * @private
|
| + * Called when the list is being removed.
|
| */
|
| - onFocus_: function() {
|
| - var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
|
| - var toggleButton = this.getToggleElement();
|
| + onRemoved: function() {
|
| + chrome.developerPrivate.onItemStateChanged.removeListener(
|
| + this.onItemStateChangedListener_);
|
| + this.clear(false);
|
| + },
|
|
|
| - if (toggleButton && !toggleButton.isShowingAll) {
|
| - var rows = this.focusGrid_.rows;
|
| - assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
|
| + /**
|
| + * Sets the active error in the list.
|
| + * @param {number} index The index to set to be active.
|
| + */
|
| + setActiveError: function(index) {
|
| + var errorList = this.querySelector('.extension-error-list-contents');
|
| + var item = errorList.children[index];
|
| + this.setActiveErrorNode_(
|
| + item ? item.querySelector('.extension-error-metadata') : null);
|
| + var node = null;
|
| + if (index >= 0 && index < errorList.children.length) {
|
| + node = errorList.children[index].querySelector(
|
| + '.extension-error-metadata');
|
| + }
|
| + this.setActiveErrorNode_(node);
|
| + },
|
|
|
| - var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
|
| - if (rows.indexOf(activeRow) < firstVisible)
|
| - activeRow = rows[firstVisible];
|
| - } else if (!activeRow) {
|
| - activeRow = this.focusGrid_.rows[0];
|
| + /**
|
| + * Clears the list of all errors.
|
| + * @param {boolean} deleteErrors Whether or not the errors should be deleted
|
| + * on the backend.
|
| + */
|
| + clear: function(deleteErrors) {
|
| + if (this.errors_.length == 0)
|
| + return;
|
| +
|
| + if (deleteErrors) {
|
| + var ids = this.errors_.map(function(error) { return error.id; });
|
| + chrome.developerPrivate.deleteExtensionErrors({
|
| + extensionId: this.extensionId_,
|
| + errorIds: ids
|
| + });
|
| }
|
|
|
| - activeRow.getEquivalentElement(null).focus();
|
| + this.setActiveErrorNode_(null);
|
| + this.errors_.length = 0;
|
| + var errorList = this.querySelector('.extension-error-list-contents');
|
| + while (errorList.firstChild)
|
| + errorList.removeChild(errorList.firstChild);
|
| },
|
|
|
| /**
|
| - * Initialize the "Show More" link for the error list. If there are more
|
| - * than |MAX_ERRORS_TO_SHOW_| errors in the list.
|
| + * Sets the active error in the list.
|
| + * @param {?} node The error to make active.
|
| * @private
|
| */
|
| - initShowMoreLink_: function() {
|
| - var link = this.querySelector(
|
| - '.extension-error-list-show-more [is="action-link"]');
|
| - link.hidden = false;
|
| - link.isShowingAll = false;
|
| -
|
| - var listContents = this.querySelector('.extension-error-list-contents');
|
| -
|
| - // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
|
| - listContents.addEventListener('webkitTransitionEnd', function(e) {
|
| - if (listContents.classList.contains('deactivating'))
|
| - listContents.classList.remove('deactivating', 'active');
|
| - else
|
| - listContents.classList.add('scrollable');
|
| - });
|
| + setActiveErrorNode_: function(node) {
|
| + if (this.activeError_)
|
| + this.activeError_.classList.remove('extension-error-active');
|
|
|
| - link.addEventListener('click', function(e) {
|
| - // Needs to be enabled in case the focused row is now hidden.
|
| - this.gridBoundary_.tabIndex = 0;
|
| + if (node)
|
| + node.classList.add('extension-error-active');
|
|
|
| - link.isShowingAll = !link.isShowingAll;
|
| + this.activeError_ = node;
|
|
|
| - var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
|
| - 'extensionErrorsShowMore';
|
| - link.textContent = loadTimeData.getString(message);
|
| + this.dispatchEvent(
|
| + new CustomEvent('activeExtensionErrorChanged',
|
| + {bubbles: true, detail: node ? node.error : null}));
|
| + },
|
|
|
| - // Disable scrolling while transitioning. If the element is active,
|
| - // scrolling is enabled when the transition ends.
|
| - listContents.classList.remove('scrollable');
|
| + /**
|
| + * 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;
|
| + },
|
|
|
| - if (link.isShowingAll) {
|
| - listContents.classList.add('active');
|
| - listContents.classList.remove('deactivating');
|
| - } else {
|
| - listContents.classList.add('deactivating');
|
| - }
|
| - }.bind(this));
|
| - }
|
| + /**
|
| + * 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 {
|
|
|