[sql] Remove _HAS_EXCEPTIONS=0 from build info.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_error.js
blobc9296554d9aea901150212419fc8e1befca30780
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() {
6 'use strict';
8 /**
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));
18 /**
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);
28 /**
29 * @param {!Array<(ManifestError|RuntimeError)>} errors
30 * @param {number} id
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)
36 return i;
38 return -1;
41 /**
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
45 * represent.
46 * @param {Element} boundary The boundary for the focus grid.
47 * @constructor
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);
54 return div;
57 ExtensionError.prototype = {
58 __proto__: cr.ui.FocusRow.prototype,
60 /** @override */
61 getEquivalentElement: function(element) {
62 if (element.classList.contains('extension-error-metadata'))
63 return this;
64 if (element.classList.contains('error-delete-button')) {
65 return /** @type {!HTMLElement} */ (this.querySelector(
66 '.error-delete-button'));
68 assertNotReached();
69 return element;
72 /**
73 * @param {(RuntimeError|ManifestError)} error The error the element should
74 * represent.
75 * @param {Element} boundary The boundary for the FocusGrid.
76 * @private
78 decorateWithError_: function(error, boundary) {
79 this.decorate(boundary);
81 /**
82 * The backing error.
83 * @type {(ManifestError|RuntimeError)}
85 this.error = error;
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');
92 break;
93 case chrome.developerPrivate.ErrorLevel.WARN:
94 this.classList.add('extension-error-severity-warning');
95 break;
96 case chrome.developerPrivate.ErrorLevel.ERROR:
97 this.classList.add('extension-error-severity-fatal');
98 break;
99 default:
100 assertNotReached();
102 } else {
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).
111 iconNode.alt = '';
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) {
119 this.dispatchEvent(
120 new CustomEvent('deleteExtensionError',
121 {bubbles: true, detail: this.error}));
122 }.bind(this));
124 this.addEventListener('click', function(e) {
125 if (e.target != deleteButton)
126 this.requestActive_();
127 }.bind(this));
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.
139 * @private
141 requestActive_: function() {
142 this.dispatchEvent(
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.
153 * @constructor
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);
161 return div;
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)>}
175 this.errors_ = [];
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) {
193 this.clear(true);
194 }.bind(this));
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);
209 }.bind(this);
211 chrome.developerPrivate.onItemStateChanged.addListener(
212 this.onItemStateChangedListener_);
215 * The active error element in the list.
216 * @private {?}
218 this.activeError_ = null;
220 this.setActiveError(0);
224 * Adds an error to the list.
225 * @param {(RuntimeError|ManifestError)} error The error to add.
226 * @private
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.
240 * @private
242 removeError_: function(error) {
243 var index = 0;
244 for (; index < this.errors_.length; ++index) {
245 if (this.errors_[index].id == error.id)
246 break;
248 assert(index != this.errors_.length);
249 var errorList = this.querySelector('.extension-error-list-contents');
251 var wasActive =
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);
258 if (wasActive) {
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,
265 errorIds: [error.id]
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
275 * errors.
276 * @private
278 updateErrors_: function(newErrors) {
279 this.errors_.forEach(function(error) {
280 if (findErrorById(newErrors, error.id) == -1)
281 this.removeError_(error);
282 }, this);
283 newErrors.forEach(function(error) {
284 var index = findErrorById(this.errors_, error.id);
285 if (index == -1)
286 this.addError_(error);
287 else
288 this.errors_[index] = error; // Update the existing reference.
289 }, this);
293 * Called when the list is being removed.
295 onRemoved: function() {
296 chrome.developerPrivate.onItemStateChanged.removeListener(
297 this.onItemStateChangedListener_);
298 this.clear(false);
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);
310 var node = 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
321 * on the backend.
323 clear: function(deleteErrors) {
324 if (this.errors_.length == 0)
325 return;
327 if (deleteErrors) {
328 var ids = this.errors_.map(function(error) { return error.id; });
329 chrome.developerPrivate.deleteExtensionErrors({
330 extensionId: this.extensionId_,
331 errorIds: ids
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.
345 * @private
347 setActiveErrorNode_: function(node) {
348 if (this.activeError_)
349 this.activeError_.classList.remove('extension-error-active');
351 if (node)
352 node.classList.add('extension-error-active');
354 this.activeError_ = node;
356 this.dispatchEvent(
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.
364 * @private
366 onFocusin_: function() {
367 this.gridBoundary_.tabIndex = -1;
371 * Focus the first focusable row when tabbing into the grid for the
372 * first time.
373 * @private
375 onFocus_: function() {
376 var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
377 activeRow.getEquivalentElement(null).focus();
381 return {
382 ExtensionErrorList: ExtensionErrorList