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

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, 8 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.
(...skipping 28 matching lines...) Expand all
39 div.__proto__ = ExtensionError.prototype; 39 div.__proto__ = ExtensionError.prototype;
40 div.decorate(error, boundary); 40 div.decorate(error, boundary);
41 return div; 41 return div;
42 } 42 }
43 43
44 ExtensionError.prototype = { 44 ExtensionError.prototype = {
45 __proto__: cr.ui.FocusRow.prototype, 45 __proto__: cr.ui.FocusRow.prototype,
46 46
47 /** @override */ 47 /** @override */
48 getEquivalentElement: function(element) { 48 getEquivalentElement: function(element) {
49 return assert(this.querySelector('.extension-error-view-details')); 49 if (element.classList.contains('extension-error-metadata'))
50 return this;
51 if (element.classList.contains('error-delete-button')) {
52 return /** @type {!HTMLElement} */ (this.querySelector(
53 '.error-delete-button'));
54 }
55 assertNotReached();
56 return element;
50 }, 57 },
51 58
52 /** 59 /**
53 * @param {(RuntimeError|ManifestError)} error The error the element should 60 * @param {(RuntimeError|ManifestError)} error The error the element should
54 * represent 61 * represent
55 * @param {Element} boundary The boundary for the FocusGrid. 62 * @param {Element} boundary The boundary for the FocusGrid.
56 * @override 63 * @override
57 */ 64 */
58 decorate: function(error, boundary) { 65 decorate: function(error, boundary) {
59 cr.ui.FocusRow.prototype.decorate.call(this, boundary); 66 cr.ui.FocusRow.prototype.decorate.call(this, boundary);
60 67
68 /**
69 * The backing error.
70 * @type {(ManifestError|RuntimeError)}
71 */
72 this.error = error;
73
61 // Add an additional class for the severity level. 74 // Add an additional class for the severity level.
62 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) { 75 if (error.type == chrome.developerPrivate.ErrorType.RUNTIME) {
63 switch (error.severity) { 76 switch (error.severity) {
64 case chrome.developerPrivate.ErrorLevel.LOG: 77 case chrome.developerPrivate.ErrorLevel.LOG:
65 this.classList.add('extension-error-severity-info'); 78 this.classList.add('extension-error-severity-info');
66 break; 79 break;
67 case chrome.developerPrivate.ErrorLevel.WARN: 80 case chrome.developerPrivate.ErrorLevel.WARN:
68 this.classList.add('extension-error-severity-warning'); 81 this.classList.add('extension-error-severity-warning');
69 break; 82 break;
70 case chrome.developerPrivate.ErrorLevel.ERROR: 83 case chrome.developerPrivate.ErrorLevel.ERROR:
71 this.classList.add('extension-error-severity-fatal'); 84 this.classList.add('extension-error-severity-fatal');
72 break; 85 break;
73 default: 86 default:
74 assertNotReached(); 87 assertNotReached();
75 } 88 }
76 } else { 89 } else {
77 // We classify manifest errors as "warnings". 90 // We classify manifest errors as "warnings".
78 this.classList.add('extension-error-severity-warning'); 91 this.classList.add('extension-error-severity-warning');
79 } 92 }
80 93
81 var iconNode = document.createElement('img'); 94 var iconNode = document.createElement('img');
82 iconNode.className = 'extension-error-icon'; 95 iconNode.className = 'extension-error-icon';
83 // TODO(hcarmona): Populate alt text with a proper description since this 96 // TODO(hcarmona): Populate alt text with a proper description since this
84 // icon conveys the severity of the error. (info, warning, fatal). 97 // icon conveys the severity of the error. (info, warning, fatal).
85 iconNode.alt = ''; 98 iconNode.alt = '';
86 this.insertBefore(iconNode, this.firstChild); 99 this.insertBefore(iconNode, this.firstChild);
87 100
88 var messageSpan = this.querySelector('.extension-error-message'); 101 var messageSpan = this.querySelector('.extension-error-message');
89 messageSpan.textContent = error.message; 102 messageSpan.textContent = error.message;
90 messageSpan.title = error.message;
91 103
92 var extensionUrl = 'chrome-extension://' + error.extensionId + '/'; 104 var deleteButton = this.querySelector('.error-delete-button');
93 var viewDetailsLink = this.querySelector('.extension-error-view-details'); 105 deleteButton.addEventListener('click', function(e) {
106 this.dispatchEvent(
107 new CustomEvent('deleteExtensionError',
108 {bubbles: true, detail: this.error}));
109 }.bind(this));
94 110
95 // If we cannot open the file source and there are no external frames in 111 this.addEventListener('click', function(e) {
96 // the stack, then there are no details to display. 112 if (e.target != deleteButton)
97 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError( 113 this.requestActive_();
98 error, extensionUrl)) { 114 }.bind(this));
99 viewDetailsLink.hidden = true; 115 this.addEventListener('keydown', function(e) {
Dan Beam 2015/04/28 03:47:04 isn't there already a synthetic click being dispat
Devlin 2015/04/29 16:08:37 In testing, no - I think only controls (buttons, e
100 } else { 116 if (e.keyIdentifier == 'Enter' && e.target != deleteButton)
101 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ? 117 this.requestActive_();
102 'extensionErrorViewManifest' : 'extensionErrorViewDetails'; 118 });
103 viewDetailsLink.textContent = loadTimeData.getString(stringId);
104 119
105 viewDetailsLink.addEventListener('click', function(e) { 120 this.addFocusableElement(this);
106 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay( 121 this.addFocusableElement(this.querySelector('.error-delete-button'));
107 error, extensionUrl); 122 },
108 });
109 123
110 this.addFocusableElement(viewDetailsLink); 124 /**
111 } 125 * Bubble up an event to request to become active.
126 * @private
127 */
128 requestActive_: function() {
129 this.dispatchEvent(
130 new CustomEvent('highlightExtensionError',
131 {bubbles: true, detail: this.error}));
112 }, 132 },
113 }; 133 };
114 134
115 /** 135 /**
116 * A variable length list of runtime or manifest errors for a given extension. 136 * A variable length list of runtime or manifest errors for a given extension.
117 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension 137 * @param {Array<(RuntimeError|ManifestError)>} errors The list of extension
118 * errors with which to populate the list. 138 * errors with which to populate the list.
139 * @param {string} extensionId The id of the extension.
119 * @constructor 140 * @constructor
120 * @extends {HTMLDivElement} 141 * @extends {HTMLDivElement}
121 */ 142 */
122 function ExtensionErrorList(errors) { 143 function ExtensionErrorList(errors, extensionId) {
123 var div = cloneTemplate('extension-error-list'); 144 var div = cloneTemplate('extension-error-list');
124 div.__proto__ = ExtensionErrorList.prototype; 145 div.__proto__ = ExtensionErrorList.prototype;
125 div.errors_ = errors; 146 div.extensionId_ = extensionId;
126 div.decorate(); 147 div.decorate(errors);
127 return div; 148 return div;
128 } 149 }
129 150
130 /**
131 * @private
132 * @const
133 * @type {number}
134 */
135 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3;
136
137 ExtensionErrorList.prototype = { 151 ExtensionErrorList.prototype = {
138 __proto__: HTMLDivElement.prototype, 152 __proto__: HTMLDivElement.prototype,
139 153
140 decorate: function() { 154 /**
155 * Initializes the extension error list.
156 * @param {Array<(RuntimeError|ManifestError)>} errors The list of errors.
157 */
158 decorate: function(errors) {
159 /**
160 * @private {Array<(ManifestError|RuntimeError)>}
161 */
162 this.errors_ = [];
163
141 this.focusGrid_ = new cr.ui.FocusGrid(); 164 this.focusGrid_ = new cr.ui.FocusGrid();
142 this.gridBoundary_ = this.querySelector('.extension-error-list-contents'); 165 this.gridBoundary_ = this.querySelector('.extension-error-list-contents');
143 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this)); 166 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this));
144 this.gridBoundary_.addEventListener('focusin', 167 this.gridBoundary_.addEventListener('focusin',
145 this.onFocusin_.bind(this)); 168 this.onFocusin_.bind(this));
169 errors.forEach(this.addError_, this);
170
171 this.addEventListener('highlightExtensionError', function(e) {
172 this.setActiveErrorNode_(e.target);
173 });
174 this.addEventListener('deleteExtensionError', function(e) {
175 this.removeError_(e.detail);
176 });
177
178 this.querySelector('#extension-error-list-clear').addEventListener(
179 'click', function(e) {
180 this.clear(true);
181 }.bind(this));
182
183 /**
184 * The callback for the extension changed event.
185 * @private {function(EventData):void}
186 */
187 this.onItemStateChangedListener_ = function(data) {
188 var type = chrome.developerPrivate.EventType;
189 if ((data.event_type == type.ERRORS_REMOVED ||
190 data.event_type == type.ERROR_ADDED) &&
191 data.extensionInfo.id == this.extensionId_) {
192 var newErrors = data.extensionInfo.runtimeErrors.concat(
193 data.extensionInfo.manifestErrors);
194 this.updateErrors_(newErrors);
195 }
196 }.bind(this);
197
198 chrome.developerPrivate.onItemStateChanged.addListener(
199 this.onItemStateChangedListener_);
200
201 /**
202 * The active error element in the list.
203 * @private {?}
204 */
205 this.activeError_ = null;
206
207 this.setActiveError(0);
208 },
209
210 /**
211 * Adds an error to the list.
212 * @param {(RuntimeError|ManifestError)} error The error to add.
213 * @private
214 */
215 addError_: function(error) {
216 this.querySelector('#no-errors-span').hidden = true;
217 this.errors_.push(error);
218 var focusRow = new ExtensionError(error, this.gridBoundary_);
219 this.gridBoundary_.appendChild(document.createElement('li')).
220 appendChild(focusRow);
221 this.focusGrid_.addRow(focusRow);
222 },
223
224 /**
225 * Removes an error from the list.
226 * @param {(RuntimeError|ManifestError)} error The error to remove.
227 * @private
228 */
229 removeError_: function(error) {
230 var index = 0;
231 for (; index < this.errors_.length; ++index) {
232 if (this.errors_[index].id == error.id)
233 break;
234 }
235 assert(index != this.errors_.length);
236 var errorList = this.querySelector('.extension-error-list-contents');
237
238 var wasActive =
239 this.activeError_ && this.activeError_.error.id == error.id;
240
241 this.errors_.splice(index, 1);
242 var listElement = errorList.children[index];
243 listElement.parentNode.removeChild(listElement);
244
245 if (wasActive) {
246 index = Math.min(index, this.errors_.length - 1);
247 this.setActiveError(index); // Gracefully handles the -1 case.
248 }
249
250 chrome.developerPrivate.deleteExtensionErrors({
251 extensionId: error.extensionId,
252 errorIds: [error.id]
253 });
254
255 if (this.errors_.length == 0)
256 this.querySelector('#no-errors-span').hidden = false;
257 },
258
259 /**
260 * Updates the list of errors.
261 * @param {Array<(ManifestError|RuntimeError)>} newErrors The new list of
262 * errors.
263 * @private
264 */
265 updateErrors_: function(newErrors) {
266 var listHasError = function(list, id) {
267 for (var i = 0; i < list.length; ++i) {
268 if (list[i].id == id)
269 return true;
270 }
271 return false;
272 };
Dan Beam 2015/04/28 03:47:03 nit: make a static method to do this: /** *
Devlin 2015/04/29 16:08:37 Done.
146 this.errors_.forEach(function(error) { 273 this.errors_.forEach(function(error) {
147 if (idIsValid(error.extensionId)) { 274 if (!listHasError(newErrors, error.id))
148 var focusRow = new ExtensionError(error, this.gridBoundary_); 275 this.removeError_(error);
149 this.gridBoundary_.appendChild(
150 document.createElement('li')).appendChild(focusRow);
151 this.focusGrid_.addRow(focusRow);
152 }
153 }, this); 276 }, this);
154 this.focusGrid_.ensureRowActive(); 277 newErrors.forEach(function(error) {
155 278 if (!listHasError(this.errors_, error.id))
156 var numShowing = this.focusGrid_.rows.length; 279 this.addError_(error);
157 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) 280 }, this);
158 this.initShowMoreLink_(); 281 },
159 }, 282
160 283 /**
161 /** 284 * Called when the list is being removed.
162 * @return {?Element} The element that toggles between show more and show 285 */
163 * less, or null if it's hidden. Button will be hidden if there are less 286 onRemoved: function() {
164 * errors than |MAX_ERRORS_TO_SHOW_|. 287 chrome.developerPrivate.onItemStateChanged.removeListener(
165 */ 288 this.onItemStateChangedListener_);
166 getToggleElement: function() { 289 this.clear(false);
167 return this.querySelector( 290 },
168 '.extension-error-list-show-more [is="action-link"]:not([hidden])'); 291
169 }, 292 /**
170 293 * Sets the active error in the list.
171 /** @return {!Element} The element containing the list of errors. */ 294 * @param {number} index The index to set to be active.
172 getErrorListElement: function() { 295 */
173 return this.gridBoundary_; 296 setActiveError: function(index) {
297 var errorList = this.querySelector('.extension-error-list-contents');
298 var item = errorList.children[index];
299 this.setActiveErrorNode_(
300 item ? item.querySelector('.extension-error-metadata') : null);
301 var node = null;
302 if (index >= 0 && index < errorList.children.length) {
303 node = errorList.children[index].querySelector(
304 '.extension-error-metadata');
305 }
306 this.setActiveErrorNode_(node);
307 },
308
309 /**
310 * Clears the list of all errors.
311 * @param {boolean} deleteErrors Whether or not the errors should be deleted
312 * on the backend.
313 */
314 clear: function(deleteErrors) {
315 if (this.errors_.length == 0)
316 return;
317
318 if (deleteErrors) {
319 var ids = this.errors_.map(function(error) { return error.id; });
320 chrome.developerPrivate.deleteExtensionErrors({
321 extensionId: this.extensionId_,
322 errorIds: ids
323 });
324 }
325
326 this.setActiveErrorNode_(null);
327 this.errors_.length = 0;
328 var errorList = this.querySelector('.extension-error-list-contents');
329 while (errorList.firstChild)
330 errorList.removeChild(errorList.firstChild);
331 },
332
333 /**
334 * Sets the active error in the list.
335 * @param {?} node The error to make active.
336 * @private
337 */
338 setActiveErrorNode_: function(node) {
339 if (this.activeError_)
340 this.activeError_.classList.remove('extension-error-active');
341
342 if (node)
343 node.classList.add('extension-error-active');
344
345 this.activeError_ = node;
346
347 this.dispatchEvent(
348 new CustomEvent('activeExtensionErrorChanged',
349 {bubbles: true, detail: node ? node.error : null}));
174 }, 350 },
175 351
176 /** 352 /**
177 * The grid should not be focusable once it or an element inside it is 353 * 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. 354 * focused. This is necessary to allow tabbing out of the grid in reverse.
179 * @private 355 * @private
180 */ 356 */
181 onFocusin_: function() { 357 onFocusin_: function() {
182 this.gridBoundary_.tabIndex = -1; 358 this.gridBoundary_.tabIndex = -1;
183 }, 359 },
184 360
185 /** 361 /**
186 * Focus the first focusable row when tabbing into the grid for the 362 * Focus the first focusable row when tabbing into the grid for the
187 * first time. 363 * first time.
188 * @private 364 * @private
189 */ 365 */
190 onFocus_: function() { 366 onFocus_: function() {
191 var activeRow = this.gridBoundary_.querySelector('.focus-row-active'); 367 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(); 368 activeRow.getEquivalentElement(null).focus();
206 }, 369 },
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 }; 370 };
252 371
253 return { 372 return {
254 ExtensionErrorList: ExtensionErrorList 373 ExtensionErrorList: ExtensionErrorList
255 }; 374 };
256 }); 375 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698