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
47 * @extends {HTMLElement}
49 function ExtensionError(error
) {
50 var div
= cloneTemplate('extension-error-metadata');
51 div
.__proto__
= ExtensionError
.prototype;
56 ExtensionError
.prototype = {
57 __proto__
: HTMLElement
.prototype,
60 * @param {(RuntimeError|ManifestError)} error The error the element should
64 decorate: function(error
) {
67 * @type {(ManifestError|RuntimeError)}
71 // Add an additional class for the severity level.
72 if (error
.type
== chrome
.developerPrivate
.ErrorType
.RUNTIME
) {
73 switch (error
.severity
) {
74 case chrome
.developerPrivate
.ErrorLevel
.LOG
:
75 this.classList
.add('extension-error-severity-info');
77 case chrome
.developerPrivate
.ErrorLevel
.WARN
:
78 this.classList
.add('extension-error-severity-warning');
80 case chrome
.developerPrivate
.ErrorLevel
.ERROR
:
81 this.classList
.add('extension-error-severity-fatal');
87 // We classify manifest errors as "warnings".
88 this.classList
.add('extension-error-severity-warning');
91 var iconNode
= document
.createElement('img');
92 iconNode
.className
= 'extension-error-icon';
93 // TODO(hcarmona): Populate alt text with a proper description since this
94 // icon conveys the severity of the error. (info, warning, fatal).
96 this.insertBefore(iconNode
, this.firstChild
);
98 var messageSpan
= this.querySelector('.extension-error-message');
99 messageSpan
.textContent
= error
.message
;
101 var deleteButton
= this.querySelector('.error-delete-button');
102 deleteButton
.addEventListener('click', function(e
) {
104 new CustomEvent('deleteExtensionError',
105 {bubbles
: true, detail
: this.error
}));
108 this.addEventListener('click', function(e
) {
109 if (e
.target
!= deleteButton
)
110 this.requestActive_();
113 this.addEventListener('keydown', function(e
) {
114 if (e
.keyIdentifier
== 'Enter' && e
.target
!= deleteButton
)
115 this.requestActive_();
120 * Bubble up an event to request to become active.
123 requestActive_: function() {
125 new CustomEvent('highlightExtensionError',
126 {bubbles
: true, detail
: this.error
}));
131 * A variable length list of runtime or manifest errors for a given extension.
132 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
133 * errors with which to populate the list.
134 * @param {string} extensionId The id of the extension.
136 * @extends {HTMLDivElement}
138 function ExtensionErrorList(errors
, extensionId
) {
139 var div
= cloneTemplate('extension-error-list');
140 div
.__proto__
= ExtensionErrorList
.prototype;
141 div
.extensionId_
= extensionId
;
142 div
.decorate(errors
);
147 * @param {!Element} root
148 * @param {?Node} boundary
150 * @extends {cr.ui.FocusRow}
152 ExtensionErrorList
.FocusRow = function(root
, boundary
) {
153 cr
.ui
.FocusRow
.call(this, root
, boundary
);
155 this.addItem('message', '.extension-error-message');
156 this.addItem('delete', '.error-delete-button');
159 ExtensionErrorList
.FocusRow
.prototype = {
160 __proto__
: cr
.ui
.FocusRow
.prototype,
163 ExtensionErrorList
.prototype = {
164 __proto__
: HTMLDivElement
.prototype,
167 * Initializes the extension error list.
168 * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
170 decorate: function(errors
) {
171 /** @private {!Array<(ManifestError|RuntimeError)>} */
174 /** @private {!cr.ui.FocusGrid} */
175 this.focusGrid_
= new cr
.ui
.FocusGrid();
177 /** @private {Element} */
178 this.listContents_
= this.querySelector('.extension-error-list-contents');
180 errors
.forEach(this.addError_
, this);
182 this.focusGrid_
.ensureRowActive();
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
);
232 var extensionError
= new ExtensionError(error
);
233 this.listContents_
.appendChild(extensionError
);
235 this.focusGrid_
.addRow(
236 new ExtensionErrorList
.FocusRow(extensionError
, this.listContents_
));
240 * Removes an error from the list.
241 * @param {(RuntimeError|ManifestError)} error The error to remove.
244 removeError_: function(error
) {
246 for (; index
< this.errors_
.length
; ++index
) {
247 if (this.errors_
[index
].id
== error
.id
)
250 assert(index
!= this.errors_
.length
);
251 var errorList
= this.querySelector('.extension-error-list-contents');
254 this.activeError_
&& this.activeError_
.error
.id
== error
.id
;
256 this.errors_
.splice(index
, 1);
257 var listElement
= errorList
.children
[index
];
259 var focusRow
= this.focusGrid_
.getRowForRoot(listElement
);
260 this.focusGrid_
.removeRow(focusRow
);
261 this.focusGrid_
.ensureRowActive();
264 // TODO(dbeam): in a world where this UI is actually used, we should
265 // probably move the focus before removing |listElement|.
266 listElement
.parentNode
.removeChild(listElement
);
269 index
= Math
.min(index
, this.errors_
.length
- 1);
270 this.setActiveError(index
); // Gracefully handles the -1 case.
273 chrome
.developerPrivate
.deleteExtensionErrors({
274 extensionId
: error
.extensionId
,
278 if (this.errors_
.length
== 0)
279 this.querySelector('#no-errors-span').hidden
= false;
283 * Updates the list of errors.
284 * @param {!Array<(ManifestError|RuntimeError)>} newErrors The new list of
288 updateErrors_: function(newErrors
) {
289 this.errors_
.forEach(function(error
) {
290 if (findErrorById(newErrors
, error
.id
) == -1)
291 this.removeError_(error
);
293 newErrors
.forEach(function(error
) {
294 var index
= findErrorById(this.errors_
, error
.id
);
296 this.addError_(error
);
298 this.errors_
[index
] = error
; // Update the existing reference.
303 * Called when the list is being removed.
305 onRemoved: function() {
306 chrome
.developerPrivate
.onItemStateChanged
.removeListener(
307 this.onItemStateChangedListener_
);
312 * Sets the active error in the list.
313 * @param {number} index The index to set to be active.
315 setActiveError: function(index
) {
316 var errorList
= this.querySelector('.extension-error-list-contents');
317 var item
= errorList
.children
[index
];
318 this.setActiveErrorNode_(
319 item
? item
.querySelector('.extension-error-metadata') : null);
321 if (index
>= 0 && index
< errorList
.children
.length
) {
322 node
= errorList
.children
[index
].querySelector(
323 '.extension-error-metadata');
325 this.setActiveErrorNode_(node
);
329 * Clears the list of all errors.
330 * @param {boolean} deleteErrors Whether or not the errors should be deleted
333 clear: function(deleteErrors
) {
334 if (this.errors_
.length
== 0)
338 var ids
= this.errors_
.map(function(error
) { return error
.id
; });
339 chrome
.developerPrivate
.deleteExtensionErrors({
340 extensionId
: this.extensionId_
,
345 this.setActiveErrorNode_(null);
346 this.errors_
.length
= 0;
347 var errorList
= this.querySelector('.extension-error-list-contents');
348 while (errorList
.firstChild
)
349 errorList
.removeChild(errorList
.firstChild
);
353 * Sets the active error in the list.
354 * @param {?} node The error to make active.
357 setActiveErrorNode_: function(node
) {
358 if (this.activeError_
)
359 this.activeError_
.classList
.remove('extension-error-active');
362 node
.classList
.add('extension-error-active');
364 this.activeError_
= node
;
367 new CustomEvent('activeExtensionErrorChanged',
368 {bubbles
: true, detail
: node
? node
.error
: null}));
373 ExtensionErrorList
: ExtensionErrorList