1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 cr.define('extensions', function() {
9 * Clone a template within the extension error template collection.
10 * @param {string} templateName The class name of the template to clone.
11 * @return {HTMLElement} The clone of the template.
13 function cloneTemplate(templateName) {
14 return /** @type {HTMLElement} */($('template-collection-extension-error').
15 querySelector('.' + templateName).cloneNode(true));
19 * Checks that an Extension ID follows the proper format (i.e., is 32
20 * characters long, is lowercase, and contains letters in the range [a, p]).
21 * @param {string} id The Extension ID to test.
22 * @return {boolean} Whether or not the ID is valid.
24 function idIsValid(id) {
25 return /^[a-p]{32}$/.test(id);
29 * Creates a new ExtensionError HTMLElement; this is used to show a
30 * notification to the user when an error is caused by an extension.
31 * @param {(RuntimeError|ManifestError)} error The error the element should
33 * @param {Element} boundary The boundary for the focus grid.
35 * @extends {cr.ui.FocusRow}
37 function ExtensionError(error, boundary) {
38 var div = cloneTemplate('extension-error-metadata');
39 div.__proto__ = ExtensionError.prototype;
40 div.decorate(error, boundary);
44 ExtensionError.prototype = {
45 __proto__: cr.ui.FocusRow.prototype,
48 getEquivalentElement: function(element) {
49 return assert(this.querySelector('.extension-error-view-details'));
53 * @param {(RuntimeError|ManifestError)} error The error the element should
55 * @param {Element} boundary The boundary for the FocusGrid.
58 decorate: function(error, boundary) {
59 cr.ui.FocusRow.prototype.decorate.call(this, boundary);
61 // Add an additional class for the severity level.
62 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
63 switch (error.severity) {
64 case chrome.developerPrivate.ErrorLevel.LOG:
65 this.classList.add('extension-error-severity-info');
67 case chrome.developerPrivate.ErrorLevel.WARN:
68 this.classList.add('extension-error-severity-warning');
70 case chrome.developerPrivate.ErrorLevel.ERROR:
71 this.classList.add('extension-error-severity-fatal');
77 // We classify manifest errors as "warnings".
78 this.classList.add('extension-error-severity-warning');
81 var iconNode = document.createElement('img');
82 iconNode.className = 'extension-error-icon';
83 // TODO(hcarmona): Populate alt text with a proper description since this
84 // icon conveys the severity of the error. (info, warning, fatal).
86 this.insertBefore(iconNode, this.firstChild);
88 var messageSpan = this.querySelector('.extension-error-message');
89 messageSpan.textContent = error.message;
90 messageSpan.title = error.message;
92 var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
93 var viewDetailsLink = this.querySelector('.extension-error-view-details');
95 // If we cannot open the file source and there are no external frames in
96 // the stack, then there are no details to display.
97 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError(
98 error, extensionUrl)) {
99 viewDetailsLink.hidden = true;
101 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ?
102 'extensionErrorViewManifest' : 'extensionErrorViewDetails';
103 viewDetailsLink.textContent = loadTimeData.getString(stringId);
105 viewDetailsLink.addEventListener('click', function(e) {
106 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay(
107 error, extensionUrl);
110 this.addFocusableElement(viewDetailsLink);
116 * A variable length list of runtime or manifest errors for a given extension.
117 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
118 * errors with which to populate the list.
120 * @extends {HTMLDivElement}
122 function ExtensionErrorList(errors) {
123 var div = cloneTemplate('extension-error-list');
124 div.__proto__ = ExtensionErrorList.prototype;
125 div.errors_ = errors;
135 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
137 ExtensionErrorList.prototype = {
138 __proto__: HTMLDivElement.prototype,
140 decorate: function() {
141 this.focusGrid_ = new cr.ui.FocusGrid();
142 this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
143 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
144 this.gridBoundary_.addEventListener('focusin',
145 this.onFocusin_.bind(this));
146 this.errors_.forEach(function(error) {
147 if (idIsValid(error.extensionId)) {
148 var focusRow = new ExtensionError(error, this.gridBoundary_);
149 this.gridBoundary_.appendChild(
150 document.createElement('li')).appendChild(focusRow);
151 this.focusGrid_.addRow(focusRow);
155 var numShowing = this.focusGrid_.rows.length;
156 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_)
157 this.initShowMoreLink_();
161 * @return {?Element} The element that toggles between show more and show
162 * less, or null if it's hidden. Button will be hidden if there are less
163 * errors than |MAX_ERRORS_TO_SHOW_|.
165 getToggleElement: function() {
166 return this.querySelector(
167 '.extension-error-list-show-more [is="action-link"]:not([hidden])');
170 /** @return {!Element} The element containing the list of errors. */
171 getErrorListElement: function() {
172 return this.gridBoundary_;
176 * The grid should not be focusable once it or an element inside it is
177 * focused. This is necessary to allow tabbing out of the grid in reverse.
180 onFocusin_: function() {
181 this.gridBoundary_.tabIndex = -1;
185 * Focus the first focusable row when tabbing into the grid for the
189 onFocus_: function() {
190 var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
191 var toggleButton = this.getToggleElement();
193 if (toggleButton && !toggleButton.isShowingAll) {
194 var rows = this.focusGrid_.rows;
195 assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
197 var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
198 if (rows.indexOf(activeRow) < firstVisible)
199 activeRow = rows[firstVisible];
200 } else if (!activeRow) {
201 activeRow = this.focusGrid_.rows[0];
204 activeRow.getEquivalentElement(null).focus();
208 * Initialize the "Show More" link for the error list. If there are more
209 * than |MAX_ERRORS_TO_SHOW_| errors in the list.
212 initShowMoreLink_: function() {
213 var link = this.querySelector(
214 '.extension-error-list-show-more [is="action-link"]');
216 link.isShowingAll = false;
218 var listContents = this.querySelector('.extension-error-list-contents');
220 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
221 listContents.addEventListener('webkitTransitionEnd', function(e) {
222 if (listContents.classList.contains('deactivating'))
223 listContents.classList.remove('deactivating', 'active');
225 listContents.classList.add('scrollable');
228 link.addEventListener('click', function(e) {
229 // Needs to be enabled in case the focused row is now hidden.
230 this.gridBoundary_.tabIndex = 0;
232 link.isShowingAll = !link.isShowingAll;
234 var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
235 'extensionErrorsShowMore';
236 link.textContent = loadTimeData.getString(message);
238 // Disable scrolling while transitioning. If the element is active,
239 // scrolling is enabled when the transition ends.
240 listContents.classList.remove('scrollable');
242 if (link.isShowingAll) {
243 listContents.classList.add('active');
244 listContents.classList.remove('deactivating');
246 listContents.classList.add('deactivating');
253 ExtensionErrorList: ExtensionErrorList