[MacViews] Show comboboxes with a native NSMenu
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_error.js
blob9ad71e8d7532913323c8a96965dc3241d6a59d49
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 * @constructor
47 * @extends {HTMLElement}
49 function ExtensionError(error) {
50 var div = cloneTemplate('extension-error-metadata');
51 div.__proto__ = ExtensionError.prototype;
52 div.decorate(error);
53 return div;
56 ExtensionError.prototype = {
57 __proto__: HTMLElement.prototype,
59 /**
60 * @param {(RuntimeError|ManifestError)} error The error the element should
61 * represent.
62 * @private
64 decorate: function(error) {
65 /**
66 * The backing error.
67 * @type {(ManifestError|RuntimeError)}
69 this.error = error;
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');
76 break;
77 case chrome.developerPrivate.ErrorLevel.WARN:
78 this.classList.add('extension-error-severity-warning');
79 break;
80 case chrome.developerPrivate.ErrorLevel.ERROR:
81 this.classList.add('extension-error-severity-fatal');
82 break;
83 default:
84 assertNotReached();
86 } else {
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).
95 iconNode.alt = '';
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) {
103 this.dispatchEvent(
104 new CustomEvent('deleteExtensionError',
105 {bubbles: true, detail: this.error}));
106 }.bind(this));
108 this.addEventListener('click', function(e) {
109 if (e.target != deleteButton)
110 this.requestActive_();
111 }.bind(this));
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.
121 * @private
123 requestActive_: function() {
124 this.dispatchEvent(
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.
135 * @constructor
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);
143 return div;
147 * @param {!Element} root
148 * @param {?Node} boundary
149 * @constructor
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)>} */
172 this.errors_ = [];
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) {
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);
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.
242 * @private
244 removeError_: function(error) {
245 var index = 0;
246 for (; index < this.errors_.length; ++index) {
247 if (this.errors_[index].id == error.id)
248 break;
250 assert(index != this.errors_.length);
251 var errorList = this.querySelector('.extension-error-list-contents');
253 var wasActive =
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();
262 focusRow.destroy();
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);
268 if (wasActive) {
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,
275 errorIds: [error.id]
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
285 * errors.
286 * @private
288 updateErrors_: function(newErrors) {
289 this.errors_.forEach(function(error) {
290 if (findErrorById(newErrors, error.id) == -1)
291 this.removeError_(error);
292 }, this);
293 newErrors.forEach(function(error) {
294 var index = findErrorById(this.errors_, error.id);
295 if (index == -1)
296 this.addError_(error);
297 else
298 this.errors_[index] = error; // Update the existing reference.
299 }, this);
303 * Called when the list is being removed.
305 onRemoved: function() {
306 chrome.developerPrivate.onItemStateChanged.removeListener(
307 this.onItemStateChangedListener_);
308 this.clear(false);
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);
320 var node = 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
331 * on the backend.
333 clear: function(deleteErrors) {
334 if (this.errors_.length == 0)
335 return;
337 if (deleteErrors) {
338 var ids = this.errors_.map(function(error) { return error.id; });
339 chrome.developerPrivate.deleteExtensionErrors({
340 extensionId: this.extensionId_,
341 errorIds: ids
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.
355 * @private
357 setActiveErrorNode_: function(node) {
358 if (this.activeError_)
359 this.activeError_.classList.remove('extension-error-active');
361 if (node)
362 node.classList.add('extension-error-active');
364 this.activeError_ = node;
366 this.dispatchEvent(
367 new CustomEvent('activeExtensionErrorChanged',
368 {bubbles: true, detail: node ? node.error : null}));
372 return {
373 ExtensionErrorList: ExtensionErrorList