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

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, 9 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;
Dan Beam 2015/03/31 00:33:49 nit: no if/else after return, i.e. if (element.
Devlin 2015/03/31 16:17:50 Done.
51 } else 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;
Dan Beam 2015/03/31 00:33:50 I couldn't really tell, but can this be @private?
Devlin 2015/03/31 16:17:51 It's used by the ExtensionErrorList (in this patch
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 }));
Dan Beam 2015/03/31 00:33:49 {\s -> { \s} -> } (everywhere)
Devlin 2015/03/31 16:17:50 Done.
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) {
100 } else { 116 if (e.keyCode == 13 /* enter */)
Dan Beam 2015/03/31 00:33:49 e.keyIdentifier == 'Enter'
Dan Beam 2015/03/31 00:33:50 isn't there already a click dispatched on Enter?
Devlin 2015/03/31 16:17:50 Thought keyIdentifier was from an old draft... Do
Devlin 2015/03/31 16:17:50 I would have thought so, but in testing, no. I wo
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));
146 this.errors_.forEach(function(error) { 169 errors.forEach(this.addError_, this);
147 if (idIsValid(error.extensionId)) { 170
148 var focusRow = new ExtensionError(error, this.gridBoundary_); 171 this.addEventListener('highlightExtensionError', function(e) {
149 this.gridBoundary_.appendChild( 172 this.setActiveErrorNode_(e.target);
150 document.createElement('li')).appendChild(focusRow); 173 });
151 this.focusGrid_.addRow(focusRow); 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();
181 }.bind(this));
182
183 chrome.developerPrivate.onItemStateChanged.addListener(
184 function(data) {
185 var type = chrome.developerPrivate.EventType;
186 if ((data.event_type == type.ERRORS_REMOVED ||
187 data.event_type == type.ERROR_ADDED) &&
188 data.info.id == this.extensionId_) {
189 var newErrors =
190 data.info.runtimeErrors.concat(data.info.manifestErrors);
191 this.updateErrors_(newErrors);
152 } 192 }
153 }, this); 193 }.bind(this));
154 194
155 var numShowing = this.focusGrid_.rows.length; 195 /**
156 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) 196 * The active error element in the list.
157 this.initShowMoreLink_(); 197 * @private {?}
Devlin 2015/03/26 21:42:00 Making this @private {ExtensionError} results in "
Dan Beam 2015/03/31 00:33:50 extensions.ExtensionError
Devlin 2015/03/31 16:17:50 Thought of that and tried it, same error (well, "U
198 */
199 this.activeError_ = null;
200
201 this.setActiveError(0);
158 }, 202 },
159 203
160 /** 204 /**
161 * @return {?Element} The element that toggles between show more and show 205 * Adds an error to the list.
162 * less, or null if it's hidden. Button will be hidden if there are less 206 * @param {(RuntimeError|ManifestError)} error The error to add.
163 * errors than |MAX_ERRORS_TO_SHOW_|. 207 * @private
164 */ 208 */
165 getToggleElement: function() { 209 addError_: function(error) {
166 return this.querySelector( 210 this.errors_.push(error);
167 '.extension-error-list-show-more [is="action-link"]:not([hidden])'); 211 var focusRow = new ExtensionError(error, this.gridBoundary_);
168 }, 212 this.gridBoundary_.appendChild(document.createElement('li')).
169 213 appendChild(focusRow);
170 /** @return {!Element} The element containing the list of errors. */ 214 this.focusGrid_.addRow(focusRow);
171 getErrorListElement: function() {
172 return this.gridBoundary_;
173 }, 215 },
174 216
175 /** 217 /**
218 * Removes an error from the list.
219 * @param {(RuntimeError|ManifestError)} error The error to remove.
220 * @private
221 */
222 removeError_: function(error) {
223 var index = this.errors_.indexOf(error);
224 var errorList = this.querySelector('.extension-error-list-contents');
225
226 var wasActive = this.activeError_ && this.activeError_.error == error;
227
228 this.errors_.splice(index, 1);
229 var listElement = errorList.children[index];
230 listElement.parentNode.removeChild(listElement);
231
232 if (wasActive) {
233 index = Math.min(index, this.errors_.length - 1);
Dan Beam 2015/03/31 00:33:50 what if this.errors_.length == 0? does -1 work?
Devlin 2015/03/31 16:17:50 Yep, line 274 (in this patch set) checks the index
234 this.setActiveError(index);
235 }
236
237 chrome.developerPrivate.deleteExtensionErrors({
238 extensionId: error.extensionId,
239 errorIds: [error.id]
240 });
241 },
242
243 /**
244 * Updates the list of errors.
245 * @param {Array<(ManifestError|RuntimeError)>} newErrors The new list of
246 * errors.
247 * @private
248 */
249 updateErrors_: function(newErrors) {
Dan Beam 2015/03/31 00:33:49 how often is this called? currently O(N) of M*N i
Devlin 2015/03/31 16:17:50 Yeah, right now it's M*N. M and N are each capped
250 var listHasError = function(list, id) {
251 for (var i = 0; i < list.length; ++i) {
252 if (list[i].id == id)
253 return true;
254 }
255 return false;
256 };
257 this.errors_.forEach(function(error) {
258 if (!listHasError(newErrors, error.id))
259 this.removeError_(error);
260 }, this);
261 newErrors.forEach(function(error) {
262 if (!listHasError(this.errors_, error.id))
263 this.addError_(error);
264 }, this);
265 },
266
267 /**
268 * Sets the active error in the list.
269 * @param {number} index The index to set to be active.
270 */
271 setActiveError: function(index) {
272 var errorList = this.querySelector('.extension-error-list-contents');
273 var node = null;
Dan Beam 2015/03/31 00:33:49 var node = errorList.children[index]; if (node)
Devlin 2015/03/31 16:17:50 That's actually different. setActiveErrorNode_()
274 if (index >= 0 && index < errorList.children.length) {
275 node = errorList.children[index].querySelector(
276 '.extension-error-metadata');
277 }
278 this.setActiveErrorNode_(node);
279 },
280
281 /**
282 * Clears the list of all errors.
283 */
284 clear: function() {
285 if (this.errors_.length == 0)
286 return;
287
288 var ids = [];
289 this.errors_.forEach(function(error) { ids.push(error.id); });
Dan Beam 2015/03/31 00:33:50 var ids = this.errors_.map(function(error) { retur
Devlin 2015/03/31 16:17:50 Done.
290 chrome.developerPrivate.deleteExtensionErrors({
291 extensionId: this.extensionId_,
292 errorIds: ids
293 });
294
295 this.setActiveErrorNode_(null);
296 this.errors_.splice(0, this.errors_.length);
Dan Beam 2015/03/31 00:33:49 this.errors_.length = 0;
Devlin 2015/03/31 16:17:51 How have I never seen this? Awesome.
297 var errorList = this.querySelector('.extension-error-list-contents');
298 while (errorList.firstChild)
299 errorList.removeChild(errorList.firstChild);
Dan Beam 2015/03/31 00:33:50 this.querySelector('.extension-error-list-contents
Devlin 2015/03/31 16:17:50 Pushing back on this. Two reasons: - *** It seems
300 },
301
302 /**
303 * Sets the active error in the list.
304 * @param {?} node The error to make active.
305 * @private
306 */
307 setActiveErrorNode_: function(node) {
308 if (this.activeError_)
309 this.activeError_.classList.remove('extension-error-active');
Dan Beam 2015/03/31 00:33:49 nit: \n
Devlin 2015/03/31 16:17:50 Done.
310 if (node)
311 node.classList.add('extension-error-active');
Dan Beam 2015/03/31 00:33:50 nit: \n
Devlin 2015/03/31 16:17:50 Done.
312 this.activeError_ = node;
313
314 this.dispatchEvent(
315 new CustomEvent('activeExtensionErrorChanged',
316 { bubbles: true, detail: node ? node.error : null }));
317 },
318
319 /**
176 * The grid should not be focusable once it or an element inside it is 320 * The grid should not be focusable once it or an element inside it is
177 * focused. This is necessary to allow tabbing out of the grid in reverse. 321 * focused. This is necessary to allow tabbing out of the grid in reverse.
178 * @private 322 * @private
179 */ 323 */
180 onFocusin_: function() { 324 onFocusin_: function() {
181 this.gridBoundary_.tabIndex = -1; 325 this.gridBoundary_.tabIndex = -1;
182 }, 326 },
183 327
184 /** 328 /**
185 * Focus the first focusable row when tabbing into the grid for the 329 * Focus the first focusable row when tabbing into the grid for the
186 * first time. 330 * first time.
187 * @private 331 * @private
188 */ 332 */
189 onFocus_: function() { 333 onFocus_: function() {
190 var activeRow = this.gridBoundary_.querySelector('.focus-row-active'); 334 var activeRow = this.gridBoundary_.querySelector('.focus-row-active');
191 var toggleButton = this.getToggleElement();
192
193 if (toggleButton && !toggleButton.isShowingAll) {
194 var rows = this.focusGrid_.rows;
195 assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_);
196
197 var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_;
198 if (rows.indexOf(activeRow) < firstVisible)
199 activeRow = rows[firstVisible];
200 } else if (!activeRow) {
201 activeRow = this.focusGrid_.rows[0];
202 }
203
204 activeRow.getEquivalentElement(null).focus(); 335 activeRow.getEquivalentElement(null).focus();
205 }, 336 },
206
207 /**
208 * Initialize the "Show More" link for the error list. If there are more
209 * than |MAX_ERRORS_TO_SHOW_| errors in the list.
210 * @private
211 */
212 initShowMoreLink_: function() {
213 var link = this.querySelector(
214 '.extension-error-list-show-more [is="action-link"]');
215 link.hidden = false;
216 link.isShowingAll = false;
217
218 var listContents = this.querySelector('.extension-error-list-contents');
219
220 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()?
221 listContents.addEventListener('webkitTransitionEnd', function(e) {
222 if (listContents.classList.contains('deactivating'))
223 listContents.classList.remove('deactivating', 'active');
224 else
225 listContents.classList.add('scrollable');
226 });
227
228 link.addEventListener('click', function(e) {
229 // Needs to be enabled in case the focused row is now hidden.
230 this.gridBoundary_.tabIndex = 0;
231
232 link.isShowingAll = !link.isShowingAll;
233
234 var message = link.isShowingAll ? 'extensionErrorsShowFewer' :
235 'extensionErrorsShowMore';
236 link.textContent = loadTimeData.getString(message);
237
238 // Disable scrolling while transitioning. If the element is active,
239 // scrolling is enabled when the transition ends.
240 listContents.classList.remove('scrollable');
241
242 if (link.isShowingAll) {
243 listContents.classList.add('active');
244 listContents.classList.remove('deactivating');
245 } else {
246 listContents.classList.add('deactivating');
247 }
248 }.bind(this));
249 }
250 }; 337 };
251 338
252 return { 339 return {
253 ExtensionErrorList: ExtensionErrorList 340 ExtensionErrorList: ExtensionErrorList
254 }; 341 };
255 }); 342 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698