Chromium Code Reviews| 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; | |
| 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) { |
|
Dan Beam
2015/04/22 21:02:38
this code is out of date
Devlin
2015/04/22 23:17:31
Resynced.
| |
| 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) { |
| 100 } else { | 116 if (e.keyIdentifier == 'Enter') |
|
Dan Beam
2015/04/22 21:02:38
probably need target != deleteButton here as well
Devlin
2015/04/22 23:17:31
Done.
| |
| 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(); | |
| 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); | |
| 192 } | |
| 193 }.bind(this)); | |
| 194 | |
| 195 /** | |
| 196 * The active error element in the list. | |
| 197 * @private {?} | |
| 198 */ | |
| 199 this.activeError_ = null; | |
| 200 | |
| 201 this.setActiveError(0); | |
| 202 }, | |
| 203 | |
| 204 /** | |
| 205 * Adds an error to the list. | |
| 206 * @param {(RuntimeError|ManifestError)} error The error to add. | |
| 207 * @private | |
| 208 */ | |
| 209 addError_: function(error) { | |
| 210 this.errors_.push(error); | |
| 211 var focusRow = new ExtensionError(error, this.gridBoundary_); | |
| 212 this.gridBoundary_.appendChild(document.createElement('li')). | |
| 213 appendChild(focusRow); | |
| 214 this.focusGrid_.addRow(focusRow); | |
| 215 }, | |
| 216 | |
| 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); | |
|
Dan Beam
2015/04/22 21:02:38
using references as IDs is kinda dubious. will er
Devlin
2015/04/22 23:17:31
Hmm... in theory, no, but in a pass-by-reference l
| |
| 224 var errorList = this.querySelector('.extension-error-list-contents'); | |
| 225 | |
| 226 var wasActive = this.activeError_ && this.activeError_.error == error; | |
|
Dan Beam
2015/04/22 21:02:38
same dubiousness with == across objects
Devlin
2015/04/22 23:17:31
Done.
| |
| 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); | |
| 234 this.setActiveError(index); // Gracefully handles the -1 case. | |
| 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) { | |
| 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 }; | |
| 146 this.errors_.forEach(function(error) { | 257 this.errors_.forEach(function(error) { |
| 147 if (idIsValid(error.extensionId)) { | 258 if (!listHasError(newErrors, error.id)) |
| 148 var focusRow = new ExtensionError(error, this.gridBoundary_); | 259 this.removeError_(error); |
| 149 this.gridBoundary_.appendChild( | |
| 150 document.createElement('li')).appendChild(focusRow); | |
| 151 this.focusGrid_.addRow(focusRow); | |
| 152 } | |
| 153 }, this); | 260 }, this); |
| 154 | 261 newErrors.forEach(function(error) { |
| 155 var numShowing = this.focusGrid_.rows.length; | 262 if (!listHasError(this.errors_, error.id)) |
| 156 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) | 263 this.addError_(error); |
| 157 this.initShowMoreLink_(); | 264 }, this); |
| 158 }, | 265 }, |
| 159 | 266 |
| 160 /** | 267 /** |
| 161 * @return {?Element} The element that toggles between show more and show | 268 * Sets the active error in the list. |
| 162 * less, or null if it's hidden. Button will be hidden if there are less | 269 * @param {number} index The index to set to be active. |
| 163 * errors than |MAX_ERRORS_TO_SHOW_|. | 270 */ |
| 164 */ | 271 setActiveError: function(index) { |
| 165 getToggleElement: function() { | 272 var errorList = this.querySelector('.extension-error-list-contents'); |
| 166 return this.querySelector( | 273 var node = null; |
| 167 '.extension-error-list-show-more [is="action-link"]:not([hidden])'); | 274 if (index >= 0 && index < errorList.children.length) { |
|
Dan Beam
2015/04/22 21:02:38
nit:
var item = errorList.children[index];
if
Devlin
2015/04/22 23:17:31
We also want to call setActiveErrorNode_ with null
| |
| 168 }, | 275 node = errorList.children[index].querySelector( |
| 169 | 276 '.extension-error-metadata'); |
|
Dan Beam
2015/04/22 21:02:38
nit:
node = errorList.children[index].querySele
Devlin
2015/04/22 23:17:31
Moot with above, but noted. :)
| |
| 170 /** @return {!Element} The element containing the list of errors. */ | 277 } |
| 171 getErrorListElement: function() { | 278 this.setActiveErrorNode_(node); |
| 172 return this.gridBoundary_; | 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 = this.errors_.map(function(error) { return error.id; }); | |
| 289 chrome.developerPrivate.deleteExtensionErrors({ | |
| 290 extensionId: this.extensionId_, | |
| 291 errorIds: ids | |
| 292 }); | |
| 293 | |
| 294 this.setActiveErrorNode_(null); | |
| 295 this.errors_.length = 0; | |
| 296 var errorList = this.querySelector('.extension-error-list-contents'); | |
|
Dan Beam
2015/04/22 21:02:38
nit:
this.querySelector('.extension-error-list-
Devlin
2015/04/22 23:17:31
Where'd that comment go....
https://codereview.chr
| |
| 297 while (errorList.firstChild) | |
| 298 errorList.removeChild(errorList.firstChild); | |
| 299 }, | |
| 300 | |
| 301 /** | |
| 302 * Sets the active error in the list. | |
| 303 * @param {?} node The error to make active. | |
| 304 * @private | |
| 305 */ | |
| 306 setActiveErrorNode_: function(node) { | |
| 307 if (this.activeError_) | |
| 308 this.activeError_.classList.remove('extension-error-active'); | |
| 309 | |
| 310 if (node) | |
| 311 node.classList.add('extension-error-active'); | |
| 312 | |
| 313 this.activeError_ = node; | |
| 314 | |
| 315 this.dispatchEvent( | |
| 316 new CustomEvent('activeExtensionErrorChanged', | |
| 317 {bubbles: true, detail: node ? node.error : null})); | |
| 173 }, | 318 }, |
| 174 | 319 |
| 175 /** | 320 /** |
| 176 * The grid should not be focusable once it or an element inside it is | 321 * 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. | 322 * focused. This is necessary to allow tabbing out of the grid in reverse. |
| 178 * @private | 323 * @private |
| 179 */ | 324 */ |
| 180 onFocusin_: function() { | 325 onFocusin_: function() { |
| 181 this.gridBoundary_.tabIndex = -1; | 326 this.gridBoundary_.tabIndex = -1; |
| 182 }, | 327 }, |
| 183 | 328 |
| 184 /** | 329 /** |
| 185 * Focus the first focusable row when tabbing into the grid for the | 330 * Focus the first focusable row when tabbing into the grid for the |
| 186 * first time. | 331 * first time. |
| 187 * @private | 332 * @private |
| 188 */ | 333 */ |
| 189 onFocus_: function() { | 334 onFocus_: function() { |
| 190 var activeRow = this.gridBoundary_.querySelector('.focus-row-active'); | 335 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(); | 336 activeRow.getEquivalentElement(null).focus(); |
| 205 }, | 337 }, |
| 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 }; | 338 }; |
| 251 | 339 |
| 252 return { | 340 return { |
| 253 ExtensionErrorList: ExtensionErrorList | 341 ExtensionErrorList: ExtensionErrorList |
| 254 }; | 342 }; |
| 255 }); | 343 }); |
| OLD | NEW |