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} error The error the element should represent.
32 * @param {Element} boundary The boundary for the focus grid.
34 * @extends {cr.ui.FocusRow}
36 function ExtensionError(error, boundary) {
37 var div = cloneTemplate('extension-error-metadata');
38 div.__proto__ = ExtensionError.prototype;
39 div.decorate(error, boundary);
43 ExtensionError.prototype = {
44 __proto__: cr.ui.FocusRow.prototype,
47 getEquivalentElement: function(element) {
48 return assert(this.querySelector('.extension-error-view-details'));
52 * @param {RuntimeError} error The error the element should represent
53 * @param {Element} boundary The boundary for the FocusGrid.
56 decorate: function(error, boundary) {
57 cr.ui.FocusRow.prototype.decorate.call(this, boundary);
59 // Add an additional class for the severity level.
61 this.classList.add('extension-error-severity-info');
62 else if (error.level == 1)
63 this.classList.add('extension-error-severity-warning');
65 this.classList.add('extension-error-severity-fatal');
67 var iconNode = document.createElement('img');
68 iconNode.className = 'extension-error-icon';
69 // TODO(hcarmona): Populate alt text with a proper description since this
70 // icon conveys the severity of the error. (info, warning, fatal).
72 this.insertBefore(iconNode, this.firstChild);
74 var messageSpan = this.querySelector('.extension-error-message');
75 messageSpan.textContent = error.message;
76 messageSpan.title = error.message;
78 var extensionUrl = 'chrome-extension://' + error.extensionId + '/';
79 var viewDetailsLink = this.querySelector('.extension-error-view-details');
81 // If we cannot open the file source and there are no external frames in
82 // the stack, then there are no details to display.
83 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError(
84 error, extensionUrl)) {
85 viewDetailsLink.hidden = true;
87 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ?
88 'extensionErrorViewManifest' : 'extensionErrorViewDetails';
89 viewDetailsLink.textContent = loadTimeData.getString(stringId);
91 viewDetailsLink.addEventListener('click', function(e) {
92 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay(
96 this.addFocusableElement(viewDetailsLink);
102 * A variable length list of runtime or manifest errors for a given extension.
103 * @param {Array<Object>} errors The list of extension errors with which
104 * to populate the list.
106 * @extends {HTMLDivElement}
108 function ExtensionErrorList(errors) {
109 var div = cloneTemplate('extension-error-list');
110 div.__proto__ = ExtensionErrorList.prototype;
111 div.errors_ = errors;
121 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
123 ExtensionErrorList.prototype = {
124 __proto__: HTMLDivElement.prototype,
126 decorate: function() {
127 this.focusGrid_ = new cr.ui.FocusGrid();
128 this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
129 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
130 this.gridBoundary_.addEventListener('focusin',
131 this.onFocusin_.bind(this));
132 this.errors_.forEach(function(error) {
133 if (idIsValid(error.extensionId)) {
134 var focusRow = new ExtensionError(error, this.gridBoundary_);
135 this.gridBoundary_.appendChild(
136 document.createElement('li')).appendChild(focusRow);
137 this.focusGrid_.addRow(focusRow);
141 var numShowing = this.focusGrid_.rows.length;
142 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_)
143 this.initShowMoreLink_();
147 * @return {?Element} The element that toggles between show more and show
148 * less, or null if it's hidden. Button will be hidden if there are less
149 * errors than |MAX_ERRORS_TO_SHOW_|.
151 getToggleElement: function() {
152 return this.querySelector(
153 '.extension-error-list-show-more [is="action-link"]:not([hidden])');
156 /** @return {!Element} The element containing the list of errors. */
157 getErrorListElement: function() {
158 return this.gridBoundary_;
162 * The grid should not be focusable once it or an element inside it is
163 * focused. This is necessary to allow tabbing out of the grid in reverse.
166 onFocusin_: function() {
167 this.gridBoundary_.tabIndex = -1;
171 * Focus the first focusable row when tabbing into the grid for the
175 onFocus_: function() {
176 var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
177 var toggleButton = this.getToggleElement();
179 if (toggleButton && !toggleButton.isShowingAll) {
180 var rows = this.focusGrid_.rows;
181 assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
183 var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
184 if (rows.indexOf(activeRow) < firstVisible)
185 activeRow = rows[firstVisible];
186 } else if (!activeRow) {
187 activeRow = this.focusGrid_.rows[0];
190 activeRow.getEquivalentElement(null).focus();
194 * Initialize the "Show More" link for the error list. If there are more
195 * than |MAX_ERRORS_TO_SHOW_| errors in the list.
198 initShowMoreLink_: function() {
199 var link = this.querySelector(
200 '.extension-error-list-show-more [is="action-link"]');
202 link.isShowingAll = false;
204 var listContents = this.querySelector('.extension-error-list-contents');
206 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
207 listContents.addEventListener('webkitTransitionEnd', function(e) {
208 if (listContents.classList.contains('deactivating'))
209 listContents.classList.remove('deactivating', 'active');
211 listContents.classList.add('scrollable');
214 link.addEventListener('click', function(e) {
215 // Needs to be enabled in case the focused row is now hidden.
216 this.gridBoundary_.tabIndex = 0;
218 link.isShowingAll = !link.isShowingAll;
220 var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
221 'extensionErrorsShowMore';
222 link.textContent = loadTimeData.getString(message);
224 // Disable scrolling while transitioning. If the element is active,
225 // scrolling is enabled when the transition ends.
226 listContents.classList.remove('scrollable');
228 if (link.isShowingAll) {
229 listContents.classList.add('active');
230 listContents.classList.remove('deactivating');
232 listContents.classList.add('deactivating');
239 ExtensionErrorList: ExtensionErrorList