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 * @param {!Array<(ManifestError|RuntimeError)>} errors
31 * @return {number} The index of the error with |id|, or -1 if not found.
33 function findErrorById(errors
, id
) {
34 for (var i
= 0; i
< errors
.length
; ++i
) {
35 if (errors
[i
].id
== id
)
42 * Creates a new ExtensionError HTMLElement; this is used to show a
43 * notification to the user when an error is caused by an extension.
44 * @param {(RuntimeError|ManifestError)} error The error the element should
46 * @param {Element} boundary The boundary for the focus grid.
48 * @extends {cr.ui.FocusRow}
50 function ExtensionError(error
, boundary
) {
51 var div
= cloneTemplate('extension-error-metadata');
52 div
.__proto__
= ExtensionError
.prototype;
53 div
.decorateWithError_(error
, boundary
);
57 ExtensionError
.prototype = {
58 __proto__
: cr
.ui
.FocusRow
.prototype,
61 getEquivalentElement: function(element
) {
62 if (element
.classList
.contains('extension-error-metadata'))
64 if (element
.classList
.contains('error-delete-button')) {
65 return /** @type {!HTMLElement} */ (this.querySelector(
66 '.error-delete-button'));
73 * @param {(RuntimeError|ManifestError)} error The error the element should
75 * @param {Element} boundary The boundary for the FocusGrid.
78 decorateWithError_: function(error
, boundary
) {
79 this.decorate(boundary
);
83 * @type {(ManifestError|RuntimeError)}
87 // Add an additional class for the severity level.
88 if (error
.type
== chrome
.developerPrivate
.ErrorType
.RUNTIME
) {
89 switch (error
.severity
) {
90 case chrome
.developerPrivate
.ErrorLevel
.LOG
:
91 this.classList
.add('extension-error-severity-info');
93 case chrome
.developerPrivate
.ErrorLevel
.WARN
:
94 this.classList
.add('extension-error-severity-warning');
96 case chrome
.developerPrivate
.ErrorLevel
.ERROR
:
97 this.classList
.add('extension-error-severity-fatal');
103 // We classify manifest errors as "warnings".
104 this.classList
.add('extension-error-severity-warning');
107 var iconNode
= document
.createElement('img');
108 iconNode
.className
= 'extension-error-icon';
109 // TODO(hcarmona): Populate alt text with a proper description since this
110 // icon conveys the severity of the error. (info, warning, fatal).
112 this.insertBefore(iconNode
, this.firstChild
);
114 var messageSpan
= this.querySelector('.extension-error-message');
115 messageSpan
.textContent
= error
.message
;
117 var deleteButton
= this.querySelector('.error-delete-button');
118 deleteButton
.addEventListener('click', function(e
) {
120 new CustomEvent('deleteExtensionError',
121 {bubbles
: true, detail
: this.error
}));
124 this.addEventListener('click', function(e
) {
125 if (e
.target
!= deleteButton
)
126 this.requestActive_();
128 this.addEventListener('keydown', function(e
) {
129 if (e
.keyIdentifier
== 'Enter' && e
.target
!= deleteButton
)
130 this.requestActive_();
133 this.addFocusableElement(this);
134 this.addFocusableElement(this.querySelector('.error-delete-button'));
138 * Bubble up an event to request to become active.
141 requestActive_: function() {
143 new CustomEvent('highlightExtensionError',
144 {bubbles
: true, detail
: this.error
}));
149 * A variable length list of runtime or manifest errors for a given extension.
150 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
151 * errors with which to populate the list.
152 * @param {string} extensionId The id of the extension.
154 * @extends {HTMLDivElement}
156 function ExtensionErrorList(errors
, extensionId
) {
157 var div
= cloneTemplate('extension-error-list');
158 div
.__proto__
= ExtensionErrorList
.prototype;
159 div
.extensionId_
= extensionId
;
160 div
.decorate(errors
);
164 ExtensionErrorList
.prototype = {
165 __proto__
: HTMLDivElement
.prototype,
168 * Initializes the extension error list.
169 * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
171 decorate: function(errors
) {
173 * @private {!Array<(ManifestError|RuntimeError)>}
177 this.focusGrid_
= new cr
.ui
.FocusGrid();
178 this.gridBoundary_
= this.querySelector('.extension-error-list-contents');
179 this.gridBoundary_
.addEventListener('focus', this.onFocus_
.bind(this));
180 this.gridBoundary_
.addEventListener('focusin',
181 this.onFocusin_
.bind(this));
182 errors
.forEach(this.addError_
, this);
184 this.addEventListener('highlightExtensionError', function(e
) {
185 this.setActiveErrorNode_(e
.target
);
187 this.addEventListener('deleteExtensionError', function(e
) {
188 this.removeError_(e
.detail
);
191 this.querySelector('#extension-error-list-clear').addEventListener(
192 'click', function(e
) {
197 * The callback for the extension changed event.
198 * @private {function(EventData):void}
200 this.onItemStateChangedListener_ = function(data
) {
201 var type
= chrome
.developerPrivate
.EventType
;
202 if ((data
.event_type
== type
.ERRORS_REMOVED
||
203 data
.event_type
== type
.ERROR_ADDED
) &&
204 data
.extensionInfo
.id
== this.extensionId_
) {
205 var newErrors
= data
.extensionInfo
.runtimeErrors
.concat(
206 data
.extensionInfo
.manifestErrors
);
207 this.updateErrors_(newErrors
);
211 chrome
.developerPrivate
.onItemStateChanged
.addListener(
212 this.onItemStateChangedListener_
);
215 * The active error element in the list.
218 this.activeError_
= null;
220 this.setActiveError(0);
224 * Adds an error to the list.
225 * @param {(RuntimeError|ManifestError)} error The error to add.
228 addError_: function(error
) {
229 this.querySelector('#no-errors-span').hidden
= true;
230 this.errors_
.push(error
);
231 var focusRow
= new ExtensionError(error
, this.gridBoundary_
);
232 this.gridBoundary_
.appendChild(document
.createElement('li')).
233 appendChild(focusRow
);
234 this.focusGrid_
.addRow(focusRow
);
238 * Removes an error from the list.
239 * @param {(RuntimeError|ManifestError)} error The error to remove.
242 removeError_: function(error
) {
244 for (; index
< this.errors_
.length
; ++index
) {
245 if (this.errors_
[index
].id
== error
.id
)
248 assert(index
!= this.errors_
.length
);
249 var errorList
= this.querySelector('.extension-error-list-contents');
252 this.activeError_
&& this.activeError_
.error
.id
== error
.id
;
254 this.errors_
.splice(index
, 1);
255 var listElement
= errorList
.children
[index
];
256 listElement
.parentNode
.removeChild(listElement
);
259 index
= Math
.min(index
, this.errors_
.length
- 1);
260 this.setActiveError(index
); // Gracefully handles the -1 case.
263 chrome
.developerPrivate
.deleteExtensionErrors({
264 extensionId
: error
.extensionId
,
268 if (this.errors_
.length
== 0)
269 this.querySelector('#no-errors-span').hidden
= false;
273 * Updates the list of errors.
274 * @param {!Array<(ManifestError|RuntimeError)>} newErrors The new list of
278 updateErrors_: function(newErrors
) {
279 this.errors_
.forEach(function(error
) {
280 if (findErrorById(newErrors
, error
.id
) == -1)
281 this.removeError_(error
);
283 newErrors
.forEach(function(error
) {
284 var index
= findErrorById(this.errors_
, error
.id
);
286 this.addError_(error
);
288 this.errors_
[index
] = error
; // Update the existing reference.
293 * Called when the list is being removed.
295 onRemoved: function() {
296 chrome
.developerPrivate
.onItemStateChanged
.removeListener(
297 this.onItemStateChangedListener_
);
302 * Sets the active error in the list.
303 * @param {number} index The index to set to be active.
305 setActiveError: function(index
) {
306 var errorList
= this.querySelector('.extension-error-list-contents');
307 var item
= errorList
.children
[index
];
308 this.setActiveErrorNode_(
309 item
? item
.querySelector('.extension-error-metadata') : null);
311 if (index
>= 0 && index
< errorList
.children
.length
) {
312 node
= errorList
.children
[index
].querySelector(
313 '.extension-error-metadata');
315 this.setActiveErrorNode_(node
);
319 * Clears the list of all errors.
320 * @param {boolean} deleteErrors Whether or not the errors should be deleted
323 clear: function(deleteErrors
) {
324 if (this.errors_
.length
== 0)
328 var ids
= this.errors_
.map(function(error
) { return error
.id
; });
329 chrome
.developerPrivate
.deleteExtensionErrors({
330 extensionId
: this.extensionId_
,
335 this.setActiveErrorNode_(null);
336 this.errors_
.length
= 0;
337 var errorList
= this.querySelector('.extension-error-list-contents');
338 while (errorList
.firstChild
)
339 errorList
.removeChild(errorList
.firstChild
);
343 * Sets the active error in the list.
344 * @param {?} node The error to make active.
347 setActiveErrorNode_: function(node
) {
348 if (this.activeError_
)
349 this.activeError_
.classList
.remove('extension-error-active');
352 node
.classList
.add('extension-error-active');
354 this.activeError_
= node
;
357 new CustomEvent('activeExtensionErrorChanged',
358 {bubbles
: true, detail
: node
? node
.error
: null}));
362 * The grid should not be focusable once it or an element inside it is
363 * focused. This is necessary to allow tabbing out of the grid in reverse.
366 onFocusin_: function() {
367 this.gridBoundary_
.tabIndex
= -1;
371 * Focus the first focusable row when tabbing into the grid for the
375 onFocus_: function() {
376 var activeRow
= this.gridBoundary_
.querySelector('.focus-row-active');
377 activeRow
.getEquivalentElement(null).focus();
382 ExtensionErrorList
: ExtensionErrorList