OLD | NEW |
---|---|
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 Loading... | |
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 }); |
OLD | NEW |