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