Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(515)

Side by Side Diff: chrome/browser/resources/extensions/extension_error.js

Issue 1016413004: [Extensions] Update Error Console UI (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 cr.define('extensions', function() { 5 cr.define('extensions', function() {
6 'use strict'; 6 'use strict';
7 7
8 /** 8 /**
9 * Clone a template within the extension error template collection. 9 * Clone a template within the extension error template collection.
10 * @param {string} templateName The class name of the template to clone. 10 * @param {string} templateName The class name of the template to clone.
11 * @return {HTMLElement} The clone of the template. 11 * @return {HTMLElement} The clone of the template.
12 */ 12 */
13 function cloneTemplate(templateName) { 13 function cloneTemplate(templateName) {
14 return /** @type {HTMLElement} */($('template-collection-extension-error'). 14 return /** @type {HTMLElement} */($('template-collection-extension-error').
15 querySelector('.' + templateName).cloneNode(true)); 15 querySelector('.' + templateName).cloneNode(true));
16 } 16 }
17 17
18 /** 18 /**
19 * Checks that an Extension ID follows the proper format (i.e., is 32 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]). 20 * characters long, is lowercase, and contains letters in the range [a, p]).
21 * @param {string} id The Extension ID to test. 21 * @param {string} id The Extension ID to test.
22 * @return {boolean} Whether or not the ID is valid. 22 * @return {boolean} Whether or not the ID is valid.
23 */ 23 */
24 function idIsValid(id) { 24 function idIsValid(id) {
25 return /^[a-p]{32}$/.test(id); 25 return /^[a-p]{32}$/.test(id);
26 } 26 }
27 27
28 /** 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.
32 */
33 function findErrorById(errors, id) {
34 for (var i = 0; i < errors.length; ++i) {
35 if (errors[i].id == id)
36 return i;
37 }
38 return -1;
39 }
40
41 /**
29 * Creates a new ExtensionError HTMLElement; this is used to show a 42 * Creates a new ExtensionError HTMLElement; this is used to show a
30 * notification to the user when an error is caused by an extension. 43 * notification to the user when an error is caused by an extension.
31 * @param {(RuntimeError|ManifestError)} error The error the element should 44 * @param {(RuntimeError|ManifestError)} error The error the element should
32 * represent. 45 * represent.
33 * @param {Element} boundary The boundary for the focus grid. 46 * @param {Element} boundary The boundary for the focus grid.
34 * @constructor 47 * @constructor
35 * @extends {cr.ui.FocusRow} 48 * @extends {cr.ui.FocusRow}
36 */ 49 */
37 function ExtensionError(error, boundary) { 50 function ExtensionError(error, boundary) {
38 var div = cloneTemplate('extension-error-metadata'); 51 var div = cloneTemplate('extension-error-metadata');
39 div.__proto__ = ExtensionError.prototype; 52 div.__proto__ = ExtensionError.prototype;
40 div.decorateWithError_(error, boundary); 53 div.decorateWithError_(error, boundary);
41 return div; 54 return div;
42 } 55 }
43 56
44 ExtensionError.prototype = { 57 ExtensionError.prototype = {
45 __proto__: cr.ui.FocusRow.prototype, 58 __proto__: cr.ui.FocusRow.prototype,
46 59
47 /** @override */ 60 /** @override */
48 getEquivalentElement: function(element) { 61 getEquivalentElement: function(element) {
49 return assert(this.querySelector('.extension-error-view-details')); 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'));
67 }
68 assertNotReached();
69 return element;
50 }, 70 },
51 71
52 /** 72 /**
53 * @param {(RuntimeError|ManifestError)} error The error the element should 73 * @param {(RuntimeError|ManifestError)} error The error the element should
54 * represent. 74 * represent.
55 * @param {Element} boundary The boundary for the FocusGrid. 75 * @param {Element} boundary The boundary for the FocusGrid.
56 * @private 76 * @private
57 */ 77 */
58 decorateWithError_: function(error, boundary) { 78 decorateWithError_: function(error, boundary) {
59 this.decorate(boundary); 79 this.decorate(boundary);
60 80
81 /**
82 * The backing error.
83 * @type {(ManifestError|RuntimeError)}
84 */
85 this.error = error;
86
61 // Add an additional class for the severity level. 87 // Add an additional class for the severity level.
62 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) { 88 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
63 switch (error.severity) { 89 switch (error.severity) {
64 case chrome.developerPrivate.ErrorLevel.LOG: 90 case chrome.developerPrivate.ErrorLevel.LOG:
65 this.classList.add('extension-error-severity-info'); 91 this.classList.add('extension-error-severity-info');
66 break; 92 break;
67 case chrome.developerPrivate.ErrorLevel.WARN: 93 case chrome.developerPrivate.ErrorLevel.WARN:
68 this.classList.add('extension-error-severity-warning'); 94 this.classList.add('extension-error-severity-warning');
69 break; 95 break;
70 case chrome.developerPrivate.ErrorLevel.ERROR: 96 case chrome.developerPrivate.ErrorLevel.ERROR:
71 this.classList.add('extension-error-severity-fatal'); 97 this.classList.add('extension-error-severity-fatal');
72 break; 98 break;
73 default: 99 default:
74 assertNotReached(); 100 assertNotReached();
75 } 101 }
76 } else { 102 } else {
77 // We classify manifest errors as "warnings". 103 // We classify manifest errors as "warnings".
78 this.classList.add('extension-error-severity-warning'); 104 this.classList.add('extension-error-severity-warning');
79 } 105 }
80 106
81 var iconNode = document.createElement('img'); 107 var iconNode = document.createElement('img');
82 iconNode.className = 'extension-error-icon'; 108 iconNode.className = 'extension-error-icon';
83 // TODO(hcarmona): Populate alt text with a proper description since this 109 // TODO(hcarmona): Populate alt text with a proper description since this
84 // icon conveys the severity of the error. (info, warning, fatal). 110 // icon conveys the severity of the error. (info, warning, fatal).
85 iconNode.alt = ''; 111 iconNode.alt = '';
86 this.insertBefore(iconNode, this.firstChild); 112 this.insertBefore(iconNode, this.firstChild);
87 113
88 var messageSpan = this.querySelector('.extension-error-message'); 114 var messageSpan = this.querySelector('.extension-error-message');
89 messageSpan.textContent = error.message; 115 messageSpan.textContent = error.message;
90 messageSpan.title = error.message;
91 116
92 var extensionUrl = 'chrome-extension://' + error.extensionId + '/'; 117 var deleteButton = this.querySelector('.error-delete-button');
93 var viewDetailsLink = this.querySelector('.extension-error-view-details'); 118 deleteButton.addEventListener('click', function(e) {
119 this.dispatchEvent(
120 new CustomEvent('deleteExtensionError',
121 {bubbles: true, detail: this.error}));
122 }.bind(this));
94 123
95 // If we cannot open the file source and there are no external frames in 124 this.addEventListener('click', function(e) {
96 // the stack, then there are no details to display. 125 if (e.target != deleteButton)
97 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError( 126 this.requestActive_();
98 error, extensionUrl)) { 127 }.bind(this));
99 viewDetailsLink.hidden = true; 128 this.addEventListener('keydown', function(e) {
100 } else { 129 if (e.keyIdentifier == 'Enter' && e.target != deleteButton)
101 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ? 130 this.requestActive_();
102 'extensionErrorViewManifest' : 'extensionErrorViewDetails'; 131 });
103 viewDetailsLink.textContent = loadTimeData.getString(stringId);
104 132
105 viewDetailsLink.addEventListener('click', function(e) { 133 this.addFocusableElement(this);
106 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay( 134 this.addFocusableElement(this.querySelector('.error-delete-button'));
107 error, extensionUrl); 135 },
108 });
109 136
110 this.addFocusableElement(viewDetailsLink); 137 /**
111 } 138 * Bubble up an event to request to become active.
139 * @private
140 */
141 requestActive_: function() {
142 this.dispatchEvent(
143 new CustomEvent('highlightExtensionError',
144 {bubbles: true, detail: this.error}));
112 }, 145 },
113 }; 146 };
114 147
115 /** 148 /**
116 * A variable length list of runtime or manifest errors for a given extension. 149 * A variable length list of runtime or manifest errors for a given extension.
117 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension 150 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
118 * errors with which to populate the list. 151 * errors with which to populate the list.
152 * @param {string} extensionId The id of the extension.
119 * @constructor 153 * @constructor
120 * @extends {HTMLDivElement} 154 * @extends {HTMLDivElement}
121 */ 155 */
122 function ExtensionErrorList(errors) { 156 function ExtensionErrorList(errors, extensionId) {
123 var div = cloneTemplate('extension-error-list'); 157 var div = cloneTemplate('extension-error-list');
124 div.__proto__ = ExtensionErrorList.prototype; 158 div.__proto__ = ExtensionErrorList.prototype;
125 div.errors_ = errors; 159 div.extensionId_ = extensionId;
126 div.decorate(); 160 div.decorate(errors);
127 return div; 161 return div;
128 } 162 }
129 163
130 /**
131 * @private
132 * @const
133 * @type {number}
134 */
135 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
136
137 ExtensionErrorList.prototype = { 164 ExtensionErrorList.prototype = {
138 __proto__: HTMLDivElement.prototype, 165 __proto__: HTMLDivElement.prototype,
139 166
140 decorate: function() { 167 /**
168 * Initializes the extension error list.
169 * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
170 */
171 decorate: function(errors) {
172 /**
173 * @private {!Array<(ManifestError|RuntimeError)>}
174 */
175 this.errors_ = [];
176
141 this.focusGrid_ = new cr.ui.FocusGrid(); 177 this.focusGrid_ = new cr.ui.FocusGrid();
142 this.gridBoundary_ = this.querySelector('.extension-error-list-contents'); 178 this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
143 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this)); 179 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
144 this.gridBoundary_.addEventListener('focusin', 180 this.gridBoundary_.addEventListener('focusin',
145 this.onFocusin_.bind(this)); 181 this.onFocusin_.bind(this));
182 errors.forEach(this.addError_, this);
183
184 this.addEventListener('highlightExtensionError', function(e) {
185 this.setActiveErrorNode_(e.target);
186 });
187 this.addEventListener('deleteExtensionError', function(e) {
188 this.removeError_(e.detail);
189 });
190
191 this.querySelector('#extension-error-list-clear').addEventListener(
192 'click', function(e) {
193 this.clear(true);
194 }.bind(this));
195
196 /**
197 * The callback for the extension changed event.
198 * @private {function(EventData):void}
199 */
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);
208 }
209 }.bind(this);
210
211 chrome.developerPrivate.onItemStateChanged.addListener(
212 this.onItemStateChangedListener_);
213
214 /**
215 * The active error element in the list.
216 * @private {?}
217 */
218 this.activeError_ = null;
219
220 this.setActiveError(0);
221 },
222
223 /**
224 * Adds an error to the list.
225 * @param {(RuntimeError|ManifestError)} error The error to add.
226 * @private
227 */
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);
235 },
236
237 /**
238 * Removes an error from the list.
239 * @param {(RuntimeError|ManifestError)} error The error to remove.
240 * @private
241 */
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;
247 }
248 assert(index != this.errors_.length);
249 var errorList = this.querySelector('.extension-error-list-contents');
250
251 var wasActive =
252 this.activeError_ && this.activeError_.error.id == error.id;
253
254 this.errors_.splice(index, 1);
255 var listElement = errorList.children[index];
256 listElement.parentNode.removeChild(listElement);
257
258 if (wasActive) {
259 index = Math.min(index, this.errors_.length - 1);
260 this.setActiveError(index); // Gracefully handles the -1 case.
261 }
262
263 chrome.developerPrivate.deleteExtensionErrors({
264 extensionId: error.extensionId,
265 errorIds: [error.id]
266 });
267
268 if (this.errors_.length == 0)
269 this.querySelector('#no-errors-span').hidden = false;
270 },
271
272 /**
273 * Updates the list of errors.
274 * @param {!Array<(ManifestError|RuntimeError)>} newErrors The new list of
275 * errors.
276 * @private
277 */
278 updateErrors_: function(newErrors) {
146 this.errors_.forEach(function(error) { 279 this.errors_.forEach(function(error) {
147 if (idIsValid(error.extensionId)) { 280 if (findErrorById(newErrors, error.id) == -1)
148 var focusRow = new ExtensionError(error, this.gridBoundary_); 281 this.removeError_(error);
149 this.gridBoundary_.appendChild(
150 document.createElement('li')).appendChild(focusRow);
151 this.focusGrid_.addRow(focusRow);
152 }
153 }, this); 282 }, this);
154 this.focusGrid_.ensureRowActive(); 283 newErrors.forEach(function(error) {
155 284 var index = findErrorById(this.errors_, error.id);
156 var numShowing = this.focusGrid_.rows.length; 285 if (index == -1)
157 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) 286 this.addError_(error);
158 this.initShowMoreLink_(); 287 else
159 }, 288 this.errors_[index] = error; // Update the existing reference.
160 289 }, this);
161 /** 290 },
162 * @return {?Element} The element that toggles between show more and show 291
163 * less, or null if it's hidden. Button will be hidden if there are less 292 /**
164 * errors than |MAX_ERRORS_TO_SHOW_|. 293 * Called when the list is being removed.
165 */ 294 */
166 getToggleElement: function() { 295 onRemoved: function() {
167 return this.querySelector( 296 chrome.developerPrivate.onItemStateChanged.removeListener(
168 '.extension-error-list-show-more [is="action-link"]:not([hidden])'); 297 this.onItemStateChangedListener_);
169 }, 298 this.clear(false);
170 299 },
171 /** @return {!Element} The element containing the list of errors. */ 300
172 getErrorListElement: function() { 301 /**
173 return this.gridBoundary_; 302 * Sets the active error in the list.
303 * @param {number} index The index to set to be active.
304 */
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');
314 }
315 this.setActiveErrorNode_(node);
316 },
317
318 /**
319 * Clears the list of all errors.
320 * @param {boolean} deleteErrors Whether or not the errors should be deleted
321 * on the backend.
322 */
323 clear: function(deleteErrors) {
324 if (this.errors_.length == 0)
325 return;
326
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
332 });
333 }
334
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);
340 },
341
342 /**
343 * Sets the active error in the list.
344 * @param {?} node The error to make active.
345 * @private
346 */
347 setActiveErrorNode_: function(node) {
348 if (this.activeError_)
349 this.activeError_.classList.remove('extension-error-active');
350
351 if (node)
352 node.classList.add('extension-error-active');
353
354 this.activeError_ = node;
355
356 this.dispatchEvent(
357 new CustomEvent('activeExtensionErrorChanged',
358 {bubbles: true, detail: node ? node.error : null}));
174 }, 359 },
175 360
176 /** 361 /**
177 * The grid should not be focusable once it or an element inside it is 362 * The grid should not be focusable once it or an element inside it is
178 * focused. This is necessary to allow tabbing out of the grid in reverse. 363 * focused. This is necessary to allow tabbing out of the grid in reverse.
179 * @private 364 * @private
180 */ 365 */
181 onFocusin_: function() { 366 onFocusin_: function() {
182 this.gridBoundary_.tabIndex = -1; 367 this.gridBoundary_.tabIndex = -1;
183 }, 368 },
184 369
185 /** 370 /**
186 * Focus the first focusable row when tabbing into the grid for the 371 * Focus the first focusable row when tabbing into the grid for the
187 * first time. 372 * first time.
188 * @private 373 * @private
189 */ 374 */
190 onFocus_: function() { 375 onFocus_: function() {
191 var activeRow = this.gridBoundary_.querySelector('.focus-row-active'); 376 var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
192 var toggleButton = this.getToggleElement();
193
194 if (toggleButton && !toggleButton.isShowingAll) {
195 var rows = this.focusGrid_.rows;
196 assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
197
198 var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
199 if (rows.indexOf(activeRow) < firstVisible)
200 activeRow = rows[firstVisible];
201 } else if (!activeRow) {
202 activeRow = this.focusGrid_.rows[0];
203 }
204
205 activeRow.getEquivalentElement(null).focus(); 377 activeRow.getEquivalentElement(null).focus();
206 }, 378 },
207
208 /**
209 * Initialize the "Show More" link for the error list. If there are more
210 * than |MAX_ERRORS_TO_SHOW_| errors in the list.
211 * @private
212 */
213 initShowMoreLink_: function() {
214 var link = this.querySelector(
215 '.extension-error-list-show-more [is="action-link"]');
216 link.hidden = false;
217 link.isShowingAll = false;
218
219 var listContents = this.querySelector('.extension-error-list-contents');
220
221 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
222 listContents.addEventListener('webkitTransitionEnd', function(e) {
223 if (listContents.classList.contains('deactivating'))
224 listContents.classList.remove('deactivating', 'active');
225 else
226 listContents.classList.add('scrollable');
227 });
228
229 link.addEventListener('click', function(e) {
230 // Needs to be enabled in case the focused row is now hidden.
231 this.gridBoundary_.tabIndex = 0;
232
233 link.isShowingAll = !link.isShowingAll;
234
235 var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
236 'extensionErrorsShowMore';
237 link.textContent = loadTimeData.getString(message);
238
239 // Disable scrolling while transitioning. If the element is active,
240 // scrolling is enabled when the transition ends.
241 listContents.classList.remove('scrollable');
242
243 if (link.isShowingAll) {
244 listContents.classList.add('active');
245 listContents.classList.remove('deactivating');
246 } else {
247 listContents.classList.add('deactivating');
248 }
249 }.bind(this));
250 }
251 }; 379 };
252 380
253 return { 381 return {
254 ExtensionErrorList: ExtensionErrorList 382 ExtensionErrorList: ExtensionErrorList
255 }; 383 };
256 }); 384 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698