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