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 10 matching lines...) Expand all Loading... |
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 * Creates a new ExtensionError HTMLElement; this is used to show a | 29 * Creates a new ExtensionError HTMLElement; this is used to show a |
30 * notification to the user when an error is caused by an extension. | 30 * notification to the user when an error is caused by an extension. |
31 * @param {Object} error The error the element should represent. | 31 * @param {RuntimeError} error The error the element should represent. |
| 32 * @param {Element} boundary The boundary for the focus grid. |
32 * @constructor | 33 * @constructor |
33 * @extends {HTMLDivElement} | 34 * @extends {cr.ui.FocusRow} |
34 */ | 35 */ |
35 function ExtensionError(error) { | 36 function ExtensionError(error, boundary) { |
36 var div = cloneTemplate('extension-error-metadata'); | 37 var div = cloneTemplate('extension-error-metadata'); |
37 div.__proto__ = ExtensionError.prototype; | 38 div.__proto__ = ExtensionError.prototype; |
38 div.decorate(error); | 39 div.decorate(error, boundary); |
39 return div; | 40 return div; |
40 } | 41 } |
41 | 42 |
42 ExtensionError.prototype = { | 43 ExtensionError.prototype = { |
43 __proto__: HTMLDivElement.prototype, | 44 __proto__: cr.ui.FocusRow.prototype, |
| 45 |
| 46 /** @override */ |
| 47 getEquivalentElement: function(element) { |
| 48 return assert(this.querySelector('.extension-error-view-details')); |
| 49 }, |
44 | 50 |
45 /** | 51 /** |
46 * @param {RuntimeError} error | 52 * @param {RuntimeError} error The error the element should represent |
| 53 * @param {Element} boundary The boundary for the FocusGrid. |
| 54 * @override |
47 */ | 55 */ |
48 decorate: function(error) { | 56 decorate: function(error, boundary) { |
| 57 cr.ui.FocusRow.prototype.decorate.call(this, boundary); |
| 58 |
49 // Add an additional class for the severity level. | 59 // Add an additional class for the severity level. |
50 if (error.level == 0) | 60 if (error.level == 0) |
51 this.classList.add('extension-error-severity-info'); | 61 this.classList.add('extension-error-severity-info'); |
52 else if (error.level == 1) | 62 else if (error.level == 1) |
53 this.classList.add('extension-error-severity-warning'); | 63 this.classList.add('extension-error-severity-warning'); |
54 else | 64 else |
55 this.classList.add('extension-error-severity-fatal'); | 65 this.classList.add('extension-error-severity-fatal'); |
56 | 66 |
57 var iconNode = document.createElement('img'); | 67 var iconNode = document.createElement('img'); |
58 iconNode.className = 'extension-error-icon'; | 68 iconNode.className = 'extension-error-icon'; |
| 69 // TODO(hcarmona): Populate alt text with a proper description since this |
| 70 // icon conveys the severity of the error. (info, warning, fatal). |
| 71 iconNode.alt = ''; |
59 this.insertBefore(iconNode, this.firstChild); | 72 this.insertBefore(iconNode, this.firstChild); |
60 | 73 |
61 var messageSpan = this.querySelector('.extension-error-message'); | 74 var messageSpan = this.querySelector('.extension-error-message'); |
62 messageSpan.textContent = error.message; | 75 messageSpan.textContent = error.message; |
63 messageSpan.title = error.message; | 76 messageSpan.title = error.message; |
64 | 77 |
65 var extensionUrl = 'chrome-extension://' + error.extensionId + '/'; | 78 var extensionUrl = 'chrome-extension://' + error.extensionId + '/'; |
66 var viewDetailsLink = this.querySelector('.extension-error-view-details'); | 79 var viewDetailsLink = this.querySelector('.extension-error-view-details'); |
67 | 80 |
68 // If we cannot open the file source and there are no external frames in | 81 // If we cannot open the file source and there are no external frames in |
69 // the stack, then there are no details to display. | 82 // the stack, then there are no details to display. |
70 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError( | 83 if (!extensions.ExtensionErrorOverlay.canShowOverlayForError( |
71 error, extensionUrl)) { | 84 error, extensionUrl)) { |
72 viewDetailsLink.hidden = true; | 85 viewDetailsLink.hidden = true; |
73 } else { | 86 } else { |
74 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ? | 87 var stringId = extensionUrl.toLowerCase() == 'manifest.json' ? |
75 'extensionErrorViewManifest' : 'extensionErrorViewDetails'; | 88 'extensionErrorViewManifest' : 'extensionErrorViewDetails'; |
76 viewDetailsLink.textContent = loadTimeData.getString(stringId); | 89 viewDetailsLink.textContent = loadTimeData.getString(stringId); |
77 | 90 |
78 viewDetailsLink.addEventListener('click', function(e) { | 91 viewDetailsLink.addEventListener('click', function(e) { |
79 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay( | 92 extensions.ExtensionErrorOverlay.getInstance().setErrorAndShowOverlay( |
80 error, extensionUrl); | 93 error, extensionUrl); |
81 }); | 94 }); |
| 95 |
| 96 this.addFocusableElement(viewDetailsLink); |
82 } | 97 } |
83 }, | 98 }, |
84 }; | 99 }; |
85 | 100 |
86 /** | 101 /** |
87 * A variable length list of runtime or manifest errors for a given extension. | 102 * A variable length list of runtime or manifest errors for a given extension. |
88 * @param {Array<Object>} errors The list of extension errors with which | 103 * @param {Array<Object>} errors The list of extension errors with which |
89 * to populate the list. | 104 * to populate the list. |
90 * @constructor | 105 * @constructor |
91 * @extends {HTMLDivElement} | 106 * @extends {HTMLDivElement} |
(...skipping 10 matching lines...) Expand all Loading... |
102 * @private | 117 * @private |
103 * @const | 118 * @const |
104 * @type {number} | 119 * @type {number} |
105 */ | 120 */ |
106 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3; | 121 ExtensionErrorList.MAX_ERRORS_TO_SHOW_ = 3; |
107 | 122 |
108 ExtensionErrorList.prototype = { | 123 ExtensionErrorList.prototype = { |
109 __proto__: HTMLDivElement.prototype, | 124 __proto__: HTMLDivElement.prototype, |
110 | 125 |
111 decorate: function() { | 126 decorate: function() { |
112 this.contents_ = this.querySelector('.extension-error-list-contents'); | 127 this.focusGrid_ = new cr.ui.FocusGrid(); |
| 128 this.gridBoundary_ = this.querySelector('.extension-error-list-contents'); |
| 129 this.gridBoundary_.addEventListener('focus', this.onFocus_.bind(this)); |
| 130 this.gridBoundary_.addEventListener('focusin', |
| 131 this.onFocusin_.bind(this)); |
113 this.errors_.forEach(function(error) { | 132 this.errors_.forEach(function(error) { |
114 if (idIsValid(error.extensionId)) { | 133 if (idIsValid(error.extensionId)) { |
115 this.contents_.appendChild(document.createElement('li')).appendChild( | 134 var focusRow = new ExtensionError(error, this.gridBoundary_); |
116 new ExtensionError(error)); | 135 this.gridBoundary_.appendChild( |
| 136 document.createElement('li')).appendChild(focusRow); |
| 137 this.focusGrid_.addRow(focusRow); |
117 } | 138 } |
118 }, this); | 139 }, this); |
119 | 140 |
120 var numShowing = this.contents_.children.length; | 141 var numShowing = this.focusGrid_.rows.length; |
121 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) | 142 if (numShowing > ExtensionErrorList.MAX_ERRORS_TO_SHOW_) |
122 this.initShowMoreLink_(); | 143 this.initShowMoreLink_(); |
123 }, | 144 }, |
124 | 145 |
125 /** | 146 /** |
| 147 * @return {?Element} The element that toggles between show more and show |
| 148 * less, or null if it's hidden. Button will be hidden if there are less |
| 149 * errors than |MAX_ERRORS_TO_SHOW_|. |
| 150 */ |
| 151 getToggleElement: function() { |
| 152 return this.querySelector( |
| 153 '.extension-error-list-show-more [is="action-link"]:not([hidden])'); |
| 154 }, |
| 155 |
| 156 /** @return {!Element} The element containing the list of errors. */ |
| 157 getErrorListElement: function() { |
| 158 return this.gridBoundary_; |
| 159 }, |
| 160 |
| 161 /** |
| 162 * The grid should not be focusable once it or an element inside it is |
| 163 * focused. This is necessary to allow tabbing out of the grid in reverse. |
| 164 * @private |
| 165 */ |
| 166 onFocusin_: function() { |
| 167 this.gridBoundary_.tabIndex = -1; |
| 168 }, |
| 169 |
| 170 /** |
| 171 * Focus the first focusable row when tabbing into the grid for the |
| 172 * first time. |
| 173 * @private |
| 174 */ |
| 175 onFocus_: function() { |
| 176 var activeRow = this.gridBoundary_.querySelector('.focus-row-active'); |
| 177 var toggleButton = this.getToggleElement(); |
| 178 |
| 179 if (toggleButton && !toggleButton.isShowingAll) { |
| 180 var rows = this.focusGrid_.rows; |
| 181 assert(rows.length > ExtensionErrorList.MAX_ERRORS_TO_SHOW_); |
| 182 |
| 183 var firstVisible = rows.length - ExtensionErrorList.MAX_ERRORS_TO_SHOW_; |
| 184 if (rows.indexOf(activeRow) < firstVisible) |
| 185 activeRow = rows[firstVisible]; |
| 186 } else if (!activeRow) { |
| 187 activeRow = this.focusGrid_.rows[0]; |
| 188 } |
| 189 |
| 190 activeRow.getEquivalentElement(null).focus(); |
| 191 }, |
| 192 |
| 193 /** |
126 * Initialize the "Show More" link for the error list. If there are more | 194 * Initialize the "Show More" link for the error list. If there are more |
127 * than |MAX_ERRORS_TO_SHOW_| errors in the list. | 195 * than |MAX_ERRORS_TO_SHOW_| errors in the list. |
128 * @private | 196 * @private |
129 */ | 197 */ |
130 initShowMoreLink_: function() { | 198 initShowMoreLink_: function() { |
131 var link = this.querySelector( | 199 var link = this.querySelector( |
132 '.extension-error-list-show-more [is="action-link"]'); | 200 '.extension-error-list-show-more [is="action-link"]'); |
133 link.hidden = false; | 201 link.hidden = false; |
134 link.isShowingAll = false; | 202 link.isShowingAll = false; |
135 | 203 |
136 var listContents = this.querySelector('.extension-error-list-contents'); | 204 var listContents = this.querySelector('.extension-error-list-contents'); |
137 | 205 |
138 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()? | 206 // TODO(dbeam/kalman): trade all this transition voodoo for .animate()? |
139 listContents.addEventListener('webkitTransitionEnd', function(e) { | 207 listContents.addEventListener('webkitTransitionEnd', function(e) { |
140 if (listContents.classList.contains('deactivating')) | 208 if (listContents.classList.contains('deactivating')) |
141 listContents.classList.remove('deactivating', 'active'); | 209 listContents.classList.remove('deactivating', 'active'); |
142 else | 210 else |
143 listContents.classList.add('scrollable'); | 211 listContents.classList.add('scrollable'); |
144 }); | 212 }); |
145 | 213 |
146 link.addEventListener('click', function(e) { | 214 link.addEventListener('click', function(e) { |
| 215 // Needs to be enabled in case the focused row is now hidden. |
| 216 this.gridBoundary_.tabIndex = 0; |
| 217 |
147 link.isShowingAll = !link.isShowingAll; | 218 link.isShowingAll = !link.isShowingAll; |
148 | 219 |
149 var message = link.isShowingAll ? 'extensionErrorsShowFewer' : | 220 var message = link.isShowingAll ? 'extensionErrorsShowFewer' : |
150 'extensionErrorsShowMore'; | 221 'extensionErrorsShowMore'; |
151 link.textContent = loadTimeData.getString(message); | 222 link.textContent = loadTimeData.getString(message); |
152 | 223 |
153 // Disable scrolling while transitioning. If the element is active, | 224 // Disable scrolling while transitioning. If the element is active, |
154 // scrolling is enabled when the transition ends. | 225 // scrolling is enabled when the transition ends. |
155 listContents.classList.remove('scrollable'); | 226 listContents.classList.remove('scrollable'); |
156 | 227 |
157 if (link.isShowingAll) { | 228 if (link.isShowingAll) { |
158 listContents.classList.add('active'); | 229 listContents.classList.add('active'); |
159 listContents.classList.remove('deactivating'); | 230 listContents.classList.remove('deactivating'); |
160 } else { | 231 } else { |
161 listContents.classList.add('deactivating'); | 232 listContents.classList.add('deactivating'); |
162 } | 233 } |
163 }.bind(this)); | 234 }.bind(this)); |
164 } | 235 } |
165 }; | 236 }; |
166 | 237 |
167 return { | 238 return { |
168 ExtensionErrorList: ExtensionErrorList | 239 ExtensionErrorList: ExtensionErrorList |
169 }; | 240 }; |
170 }); | 241 }); |
OLD | NEW |