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