Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(121)

Side by Side Diff: chrome/browser/resources/extensions/extension_list.js

Issue 916243002: Enable keyboard shortcuts for chrome://extensions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Apply Dan's Feedback Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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 <include src="extension_error.js"> 5 <include src="extension_error.js">
6 6
7 /** 7 /**
8 * The type of the extension data object. The definition is based on 8 * The type of the extension data object. The definition is based on
9 * chrome/browser/ui/webui/extensions/extension_basic_info.cc 9 * chrome/browser/ui/webui/extensions/extension_basic_info.cc
10 * and 10 * and
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
58 * version: string, 58 * version: string,
59 * views: Array<{renderViewId: number, renderProcessId: number, 59 * views: Array<{renderViewId: number, renderProcessId: number,
60 * path: string, incognito: boolean, 60 * path: string, incognito: boolean,
61 * generatedBackgroundPage: boolean}>, 61 * generatedBackgroundPage: boolean}>,
62 * wantsErrorCollection: boolean, 62 * wantsErrorCollection: boolean,
63 * wantsFileAccess: boolean, 63 * wantsFileAccess: boolean,
64 * warnings: (Array|undefined)}} 64 * warnings: (Array|undefined)}}
65 */ 65 */
66 var ExtensionData; 66 var ExtensionData;
67 67
68 ///////////////////////////////////////////////////////////////////////////////
69 // ExtensionFocusRow:
70
71 /**
72 * Provides an implementation for a single column grid.
73 * @constructor
74 * @extends {cr.ui.FocusRow}
75 */
76 function ExtensionFocusRow() {}
77
78 /**
79 * Decorates |focusRow| so that it can be treated as a ExtensionFocusRow.
80 * @param {Element} focusRow The element that has all the columns.
81 * @param {Node} boundary Focus events are ignored outside of this node.
82 */
83 ExtensionFocusRow.decorate = function(focusRow, boundary) {
84 focusRow.__proto__ = ExtensionFocusRow.prototype;
85 focusRow.decorate(boundary);
86 };
87
88 ExtensionFocusRow.prototype = {
89 __proto__: cr.ui.FocusRow.prototype,
90
91 /** @override */
92 getEquivalentElement: function(element) {
93 if (this.focusableElements.indexOf(element) > -1)
94 return element;
95
96 // All elements default to another element with the same type.
97 var columnType = element.getAttribute('column-type');
98 var equivalent = this.querySelector('[column-type=' + columnType + ']');
99
100 if (!equivalent || !this.canAddElement_(equivalent)) {
101 var actionLinks = ['options', 'website', 'launch', 'localReload'];
102 var optionalControls = ['showButton', 'incognito', 'dev-collectErrors',
103 'allUrls', 'localUrls'];
104 var removeStyleButtons = ['trash', 'enterprise'];
105 var enableControls = ['terminatedReload', 'repair', 'enabled'];
106
107 if (actionLinks.indexOf(columnType) > -1)
108 equivalent = this.getFirstFocusableByType_(actionLinks);
109 else if (optionalControls.indexOf(columnType) > -1)
110 equivalent = this.getFirstFocusableByType_(optionalControls);
111 else if (removeStyleButtons.indexOf(columnType) > -1)
112 equivalent = this.getFirstFocusableByType_(removeStyleButtons);
113 else if (enableControls.indexOf(columnType) > -1)
114 equivalent = this.getFirstFocusableByType_(enableControls);
115 }
116
117 // Return the first focusable element if no equivalent type is found.
118 return equivalent || this.focusableElements[0];
119 },
120
121 /** Updates the list of focusable elements. */
122 updateFocusableElements: function() {
123 this.focusableElements.length = 0;
124
125 var focusableCandidates = this.querySelectorAll('[column-type]');
126 for (var i = 0; i < focusableCandidates.length; ++i) {
127 var element = focusableCandidates[i];
128 if (this.canAddElement_(element))
129 this.addFocusableElement(element);
130 }
131 },
132
133 /**
134 * Get the first focusable element that matches a list of types.
135 * @param {Array<string>} types An array of types to match from.
136 * @return {?Element} Return the first element that matches a type in |types|.
137 * @private
138 */
139 getFirstFocusableByType_: function(types) {
140 for (var i = 0; i < this.focusableElements.length; ++i) {
141 var element = this.focusableElements[i];
142 if (types.indexOf(element.getAttribute('column-type')) > -1)
143 return element;
144 }
145 return null;
146 },
147
148 /**
149 * Adds a listener to an element and sets the column-type so it can be
150 * a focusable column in this row.
151 * @param {string} query A query to select an element.
152 * @param {string} columnType A tag used to identify the column when
153 * changing focus.
154 * @param {string} eventType The type of event to listen to.
Dan Beam 2015/02/20 00:22:15 why are we listening to |eventType|?
hcarmona 2015/02/20 23:23:24 Updated comment to be more descriptive.
155 * @param {function(Event)} handler The function that should be called
156 * by the event.
Dan Beam 2015/02/20 00:22:15 why is this handler needed?
hcarmona 2015/02/20 23:23:24 Updated comment to be more descriptive.
157 * @private
158 */
159 setupColumn: function(query, columnType, eventType, handler) {
160 var element = this.querySelector(query);
161 element.addEventListener(eventType, handler);
162 element.setAttribute('column-type', columnType);
163 },
164
165 /**
166 * @param {Element} element
167 * @return {boolean}
168 * @private
169 */
170 canAddElement_: function(element) {
171 if (!element || element.disabled)
172 return false;
173
174 var developerMode = $('extension-settings').classList.contains('dev-mode');
175 if (this.isDeveloperOption_(element) && !developerMode)
176 return false;
177
178 for (var el = element; el; el = el.parentElement) {
179 if (el.hidden)
180 return false;
181 }
182
183 return true;
184 },
185
186 /**
187 * Returns true if the element should only be shown in developer mode.
188 * @param {Element} element
189 * @return {boolean}
190 * @private
191 */
192 isDeveloperOption_: function(element) {
193 return /^dev-/.test(element.getAttribute('column-type'));
194 },
195 };
196
68 cr.define('options', function() { 197 cr.define('options', function() {
69 'use strict'; 198 'use strict';
70 199
71 /** 200 /**
72 * Creates a new list of extensions. 201 * Creates a new list of extensions.
73 * @param {Object=} opt_propertyBag Optional properties. 202 * @param {Object=} opt_propertyBag Optional properties.
74 * @constructor 203 * @constructor
75 * @extends {HTMLDivElement} 204 * @extends {HTMLDivElement}
76 */ 205 */
77 var ExtensionsList = cr.ui.define('div'); 206 var ExtensionsList = cr.ui.define('div');
(...skipping 11 matching lines...) Expand all
89 218
90 /** 219 /**
91 * Indicates whether an embedded options page that was navigated to through 220 * Indicates whether an embedded options page that was navigated to through
92 * the '?options=' URL query has been shown to the user. This is necessary 221 * the '?options=' URL query has been shown to the user. This is necessary
93 * to prevent showExtensionNodes_ from opening the options more than once. 222 * to prevent showExtensionNodes_ from opening the options more than once.
94 * @type {boolean} 223 * @type {boolean}
95 * @private 224 * @private
96 */ 225 */
97 optionsShown_: false, 226 optionsShown_: false,
98 227
228 /** @private {!cr.ui.FocusGrid} */
229 focusGrid_: new cr.ui.FocusGrid(),
230
99 /** 231 /**
100 * Necessary to only show the butterbar once. 232 * Necessary to only show the butterbar once.
101 * @private {boolean} 233 * @private {boolean}
102 */ 234 */
103 butterbarShown_: false, 235 butterbarShown_: false,
104 236
105 decorate: function() { 237 decorate: function() {
106 this.showExtensionNodes_(); 238 this.showExtensionNodes_();
107 }, 239 },
108 240
109 getIdQueryParam_: function() { 241 getIdQueryParam_: function() {
110 return parseQueryParams(document.location)['id']; 242 return parseQueryParams(document.location)['id'];
111 }, 243 },
112 244
113 getOptionsQueryParam_: function() { 245 getOptionsQueryParam_: function() {
114 return parseQueryParams(document.location)['options']; 246 return parseQueryParams(document.location)['options'];
115 }, 247 },
116 248
117 /** 249 /**
118 * Creates all extension items from scratch. 250 * Creates or updates all extension items from scratch.
119 * @private 251 * @private
120 */ 252 */
121 showExtensionNodes_: function() { 253 showExtensionNodes_: function() {
254 // Remove the rows from |focusGrid_| without destroying them.
255 this.focusGrid_.rows.length = 0;
256
122 // Any node that is not updated will be removed. 257 // Any node that is not updated will be removed.
123 var seenIds = []; 258 var seenIds = [];
124 259
125 // Iterate over the extension data and add each item to the list. 260 // Iterate over the extension data and add each item to the list.
126 this.data_.extensions.forEach(function(extension, i) { 261 this.data_.extensions.forEach(function(extension, i) {
127 var nextExt = this.data_.extensions[i + 1]; 262 var nextExt = this.data_.extensions[i + 1];
128 var node = $(extension.id); 263 var node = $(extension.id);
129 seenIds.push(extension.id); 264 seenIds.push(extension.id);
130 265
131 if (node) 266 if (node)
132 this.updateNode_(extension, node); 267 this.updateNode_(extension, node);
133 else 268 else
134 this.createNode_(extension, nextExt ? $(nextExt.id) : null); 269 this.createNode_(extension, nextExt ? $(nextExt.id) : null);
135 }, this); 270 }, this);
136 271
137 // Remove extensions that are no longer installed. 272 // Remove extensions that are no longer installed.
138 var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]'); 273 var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]');
139 for (var i = 0; i < nodes.length; ++i) { 274 for (var i = 0; i < nodes.length; ++i) {
140 var node = nodes[i]; 275 var node = nodes[i];
141 if (seenIds.indexOf(node.id) < 0) 276 if (seenIds.indexOf(node.id) < 0) {
142 node.parentElement.removeChild(node); 277 node.parentElement.removeChild(node);
278 // Unregister the removed node from events.
279 node.destroy();
280 }
143 } 281 }
144 282
145 var idToHighlight = this.getIdQueryParam_(); 283 var idToHighlight = this.getIdQueryParam_();
146 if (idToHighlight && $(idToHighlight)) 284 if (idToHighlight && $(idToHighlight))
147 this.scrollToNode_(idToHighlight); 285 this.scrollToNode_(idToHighlight);
148 286
149 var idToOpenOptions = this.getOptionsQueryParam_(); 287 var idToOpenOptions = this.getOptionsQueryParam_();
150 if (idToOpenOptions && $(idToOpenOptions)) 288 if (idToOpenOptions && $(idToOpenOptions))
151 this.showEmbeddedExtensionOptions_(idToOpenOptions, true); 289 this.showEmbeddedExtensionOptions_(idToOpenOptions, true);
152 290
153 var noExtensions = this.data_.extensions.length == 0; 291 var noExtensions = this.data_.extensions.length == 0;
154 this.classList.toggle('empty-extension-list', noExtensions); 292 this.classList.toggle('empty-extension-list', noExtensions);
155 }, 293 },
156 294
295 /** Updates each row's focusable elements without rebuilding the grid. */
296 updateFocusableElements: function() {
297 var rows = document.querySelectorAll('.extension-list-item-wrapper[id]');
298 for (var i = 0; i < rows.length; ++i) {
299 rows[i].updateFocusableElements();
300 }
301 },
302
157 /** 303 /**
158 * Scrolls the page down to the extension node with the given id. 304 * Scrolls the page down to the extension node with the given id.
159 * @param {string} extensionId The id of the extension to scroll to. 305 * @param {string} extensionId The id of the extension to scroll to.
160 * @private 306 * @private
161 */ 307 */
162 scrollToNode_: function(extensionId) { 308 scrollToNode_: function(extensionId) {
163 // Scroll offset should be calculated slightly higher than the actual 309 // Scroll offset should be calculated slightly higher than the actual
164 // offset of the element being scrolled to, so that it ends up not all 310 // offset of the element being scrolled to, so that it ends up not all
165 // the way at the top. That way it is clear that there are more elements 311 // the way at the top. That way it is clear that there are more elements
166 // above the element being scrolled to. 312 // above the element being scrolled to.
167 var scrollFudge = 1.2; 313 var scrollFudge = 1.2;
168 var scrollTop = $(extensionId).offsetTop - scrollFudge * 314 var scrollTop = $(extensionId).offsetTop - scrollFudge *
169 $(extensionId).clientHeight; 315 $(extensionId).clientHeight;
170 setScrollTopForDocument(document, scrollTop); 316 setScrollTopForDocument(document, scrollTop);
171 }, 317 },
172 318
173 /** 319 /**
174 * Synthesizes and initializes an HTML element for the extension metadata 320 * Synthesizes and initializes an HTML element for the extension metadata
175 * given in |extension|. 321 * given in |extension|.
176 * @param {ExtensionData} extension A dictionary of extension metadata. 322 * @param {!ExtensionData} extension A dictionary of extension metadata.
177 * @param {?Element} nextNode |node| should be inserted before |nextNode|. 323 * @param {?Element} nextNode |node| should be inserted before |nextNode|.
178 * |node| will be appended to the end if |nextNode| is null. 324 * |node| will be appended to the end if |nextNode| is null.
179 * @private 325 * @private
180 */ 326 */
181 createNode_: function(extension, nextNode) { 327 createNode_: function(extension, nextNode) {
182 var template = $('template-collection').querySelector( 328 var template = $('template-collection').querySelector(
183 '.extension-list-item-wrapper'); 329 '.extension-list-item-wrapper');
184 var node = template.cloneNode(true); 330 var node = template.cloneNode(true);
185 node.id = extension.id; 331 ExtensionFocusRow.decorate(node, $('extension-settings-list'));
332
333 var row = assertInstanceof(node, ExtensionFocusRow);
334 row.id = extension.id;
186 335
187 // The 'Show Browser Action' button. 336 // The 'Show Browser Action' button.
188 this.addListener_('click', node, '.show-button', function(e) { 337 row.setupColumn('.show-button', 'showButton', 'click', function(e) {
189 chrome.send('extensionSettingsShowButton', [extension.id]); 338 chrome.send('extensionSettingsShowButton', [extension.id]);
190 }); 339 });
191 340
192 // The 'allow in incognito' checkbox. 341 // The 'allow in incognito' checkbox.
193 this.addListener_('change', node, '.incognito-control input', 342 row.setupColumn('.incognito-control input', 'incognito', 'change',
194 function(e) { 343 function(e) {
195 var butterBar = node.querySelector('.butter-bar'); 344 var butterBar = row.querySelector('.butter-bar');
196 var checked = e.target.checked; 345 var checked = e.target.checked;
197 if (!this.butterbarShown_) { 346 if (!this.butterbarShown_) {
198 butterBar.hidden = !checked || extension.is_hosted_app; 347 butterBar.hidden = !checked || extension.is_hosted_app;
199 this.butterbarShown_ = !butterBar.hidden; 348 this.butterbarShown_ = !butterBar.hidden;
200 } else 349 } else
201 butterBar.hidden = true; 350 butterBar.hidden = true;
202 chrome.send('extensionSettingsEnableIncognito', 351 chrome.send('extensionSettingsEnableIncognito',
203 [extension.id, String(checked)]); 352 [extension.id, String(checked)]);
204 }.bind(this)); 353 }.bind(this));
205 354
206 // The 'collect errors' checkbox. This should only be visible if the 355 // The 'collect errors' checkbox. This should only be visible if the
207 // error console is enabled - we can detect this by the existence of the 356 // error console is enabled - we can detect this by the existence of the
208 // |errorCollectionEnabled| property. 357 // |errorCollectionEnabled| property.
209 this.addListener_('change', node, '.error-collection-control input', 358 row.setupColumn('.error-collection-control input', 'dev-collectErrors',
210 function(e) { 359 'change', function(e) {
211 chrome.send('extensionSettingsEnableErrorCollection', 360 chrome.send('extensionSettingsEnableErrorCollection',
212 [extension.id, String(e.target.checked)]); 361 [extension.id, String(e.target.checked)]);
213 }); 362 });
214 363
215 // The 'allow on all urls' checkbox. This should only be visible if 364 // The 'allow on all urls' checkbox. This should only be visible if
216 // active script restrictions are enabled. If they are not enabled, no 365 // active script restrictions are enabled. If they are not enabled, no
217 // extensions should want all urls. 366 // extensions should want all urls.
218 this.addListener_('click', node, '.all-urls-control', function(e) { 367 row.setupColumn('.all-urls-control input', 'allUrls', 'click',
368 function(e) {
219 chrome.send('extensionSettingsAllowOnAllUrls', 369 chrome.send('extensionSettingsAllowOnAllUrls',
220 [extension.id, String(e.target.checked)]); 370 [extension.id, String(e.target.checked)]);
221 }); 371 });
222 372
223 // The 'allow file:// access' checkbox. 373 // The 'allow file:// access' checkbox.
224 this.addListener_('click', node, '.file-access-control', function(e) { 374 row.setupColumn('.file-access-control input', 'localUrls', 'click',
375 function(e) {
225 chrome.send('extensionSettingsAllowFileAccess', 376 chrome.send('extensionSettingsAllowFileAccess',
226 [extension.id, String(e.target.checked)]); 377 [extension.id, String(e.target.checked)]);
227 }); 378 });
228 379
229 // The 'Options' button or link, depending on its behaviour. 380 // The 'Options' button or link, depending on its behaviour.
230 // Set an href to get the correct mouse-over appearance (link, 381 // Set an href to get the correct mouse-over appearance (link,
231 // footer) - but the actual link opening is done through chrome.send 382 // footer) - but the actual link opening is done through chrome.send
232 // with a preventDefault(). 383 // with a preventDefault().
233 node.querySelector('.options-link').href = extension.optionsPageHref; 384 row.querySelector('.options-link').href = extension.optionsPageHref;
234 this.addListener_('click', node, '.options-link', function(e) { 385 row.setupColumn('.options-link', 'options', 'click', function(e) {
235 chrome.send('extensionSettingsOptions', [extension.id]); 386 chrome.send('extensionSettingsOptions', [extension.id]);
236 e.preventDefault(); 387 e.preventDefault();
237 }); 388 });
238 389
239 this.addListener_('click', node, '.options-button', function(e) { 390 row.setupColumn('.options-button', 'options', 'click', function(e) {
240 this.showEmbeddedExtensionOptions_(extension.id, false); 391 this.showEmbeddedExtensionOptions_(extension.id, false);
241 e.preventDefault(); 392 e.preventDefault();
242 }.bind(this)); 393 }.bind(this));
243 394
395 // The 'View in Web Store/View Web Site' link.
396 row.querySelector('.site-link').setAttribute('column-type', 'website');
397
244 // The 'Permissions' link. 398 // The 'Permissions' link.
245 this.addListener_('click', node, '.permissions-link', function(e) { 399 row.setupColumn('.permissions-link', 'details', 'click', function(e) {
246 chrome.send('extensionSettingsPermissions', [extension.id]); 400 chrome.send('extensionSettingsPermissions', [extension.id]);
247 e.preventDefault(); 401 e.preventDefault();
248 }); 402 });
249 403
250 // The 'Reload' link. 404 // The 'Reload' link.
251 this.addListener_('click', node, '.reload-link', function(e) { 405 row.setupColumn('.reload-link', 'localReload', 'click', function(e) {
252 chrome.send('extensionSettingsReload', [extension.id]); 406 chrome.send('extensionSettingsReload', [extension.id]);
253 extensionReloadedTimestamp[extension.id] = Date.now(); 407 extensionReloadedTimestamp[extension.id] = Date.now();
254 }); 408 });
255 409
256 // The 'Launch' link. 410 // The 'Launch' link.
257 this.addListener_('click', node, '.launch-link', function(e) { 411 row.setupColumn('.launch-link', 'launch', 'click', function(e) {
258 chrome.send('extensionSettingsLaunch', [extension.id]); 412 chrome.send('extensionSettingsLaunch', [extension.id]);
259 }); 413 });
260 414
261 // The 'Reload' terminated link. 415 // The 'Reload' terminated link.
262 this.addListener_('click', node, '.terminated-reload-link', function(e) { 416 row.setupColumn('.terminated-reload-link', 'terminatedReload', 'click',
417 function(e) {
263 chrome.send('extensionSettingsReload', [extension.id]); 418 chrome.send('extensionSettingsReload', [extension.id]);
264 }); 419 });
265 420
266 // The 'Repair' corrupted link. 421 // The 'Repair' corrupted link.
267 this.addListener_('click', node, '.corrupted-repair-button', function(e) { 422 row.setupColumn('.corrupted-repair-button', 'repair', 'click',
423 function(e) {
268 chrome.send('extensionSettingsRepair', [extension.id]); 424 chrome.send('extensionSettingsRepair', [extension.id]);
269 }); 425 });
270 426
271 // The 'Enabled' checkbox. 427 // The 'Enabled' checkbox.
272 this.addListener_('change', node, '.enable-checkbox input', function(e) { 428 row.setupColumn('.enable-checkbox input', 'enabled', 'change',
429 function(e) {
273 var checked = e.target.checked; 430 var checked = e.target.checked;
274 chrome.send('extensionSettingsEnable', [extension.id, String(checked)]); 431 chrome.send('extensionSettingsEnable', [extension.id, String(checked)]);
275 432
276 // This may seem counter-intuitive (to not set/clear the checkmark) 433 // This may seem counter-intuitive (to not set/clear the checkmark)
277 // but this page will be updated asynchronously if the extension 434 // but this page will be updated asynchronously if the extension
278 // becomes enabled/disabled. It also might not become enabled or 435 // becomes enabled/disabled. It also might not become enabled or
279 // disabled, because the user might e.g. get prompted when enabling 436 // disabled, because the user might e.g. get prompted when enabling
280 // and choose not to. 437 // and choose not to.
281 e.preventDefault(); 438 e.preventDefault();
282 }); 439 });
283 440
284 // 'Remove' button. 441 // 'Remove' button.
285 var trashTemplate = $('template-collection').querySelector('.trash'); 442 var trashTemplate = $('template-collection').querySelector('.trash');
286 var trash = trashTemplate.cloneNode(true); 443 var trash = trashTemplate.cloneNode(true);
287 trash.title = loadTimeData.getString('extensionUninstall'); 444 trash.title = loadTimeData.getString('extensionUninstall');
445 trash.hidden = extension.managedInstall;
446 trash.setAttribute('column-type', 'trash');
288 trash.addEventListener('click', function(e) { 447 trash.addEventListener('click', function(e) {
289 chrome.send('extensionSettingsUninstall', [extension.id]); 448 chrome.send('extensionSettingsUninstall', [extension.id]);
290 }); 449 });
291 node.querySelector('.enable-controls').appendChild(trash); 450 row.querySelector('.enable-controls').appendChild(trash);
292 451
293 // Developer mode //////////////////////////////////////////////////////// 452 // Developer mode ////////////////////////////////////////////////////////
294 453
295 // The path, if provided by unpacked extension. 454 // The path, if provided by unpacked extension.
296 this.addListener_('click', node, '.load-path a:first-of-type', 455 row.setupColumn('.load-path a:first-of-type', 'dev-loadPath', 'click',
297 function(e) { 456 function(e) {
298 chrome.send('extensionSettingsShowPath', [String(extension.id)]); 457 chrome.send('extensionSettingsShowPath', [String(extension.id)]);
299 e.preventDefault(); 458 e.preventDefault();
300 }); 459 });
301 460
302 // Maintain the order that nodes should be in when creating as well as 461 // Maintain the order that nodes should be in when creating as well as
303 // when adding only one new node. 462 // when adding only one new row.
304 this.insertBefore(node, nextNode); 463 this.insertBefore(row, nextNode);
305 this.updateNode_(extension, node); 464 this.updateNode_(extension, row);
306 }, 465 },
307 466
308 /** 467 /**
309 * Updates an HTML element for the extension metadata given in |extension|. 468 * Updates an HTML element for the extension metadata given in |extension|.
310 * @param {ExtensionData} extension A dictionary of extension metadata. 469 * @param {!ExtensionData} extension A dictionary of extension metadata.
311 * @param {Element} node The node that is being updated. 470 * @param {!ExtensionFocusRow} row The node that is being updated.
312 * @private 471 * @private
313 */ 472 */
314 updateNode_: function(extension, node) { 473 updateNode_: function(extension, row) {
315 node.classList.toggle('inactive-extension', 474 var isActive = extension.enabled && !extension.terminated;
316 !extension.enabled || extension.terminated);
317 475
476 row.classList.toggle('inactive-extension', !isActive);
477
478 // Hack to keep the closure compiler happy about |remove|.
479 // TODO(hcarmona): Remove this hack when the closure compiler is updated.
480 var node = /** @type {Element} */ (row);
318 node.classList.remove('policy-controlled', 'may-not-modify', 481 node.classList.remove('policy-controlled', 'may-not-modify',
319 'may-not-remove'); 482 'may-not-remove');
320 var classes = []; 483 var classes = [];
321 if (extension.managedInstall) { 484 if (extension.managedInstall) {
322 classes.push('policy-controlled', 'may-not-modify'); 485 classes.push('policy-controlled', 'may-not-modify');
323 } else if (extension.dependentExtensions.length > 0) { 486 } else if (extension.dependentExtensions.length > 0) {
324 classes.push('may-not-remove', 'may-not-modify'); 487 classes.push('may-not-remove', 'may-not-modify');
325 } else if (extension.recommendedInstall) { 488 } else if (extension.recommendedInstall) {
326 classes.push('may-not-remove'); 489 classes.push('may-not-remove');
327 } else if (extension.suspiciousInstall || 490 } else if (extension.suspiciousInstall ||
328 extension.corruptInstall || 491 extension.corruptInstall ||
329 extension.updateRequiredByPolicy) { 492 extension.updateRequiredByPolicy) {
330 classes.push('may-not-modify'); 493 classes.push('may-not-modify');
331 } 494 }
332 node.classList.add.apply(node.classList, classes); 495 row.classList.add.apply(row.classList, classes);
333 496
334 node.classList.toggle('extension-highlight', 497 row.classList.toggle('extension-highlight',
335 node.id == this.getIdQueryParam_()); 498 row.id == this.getIdQueryParam_());
336 499
337 var item = node.querySelector('.extension-list-item'); 500 var item = row.querySelector('.extension-list-item');
338 // Prevent the image cache of extension icon by using the reloaded 501 // Prevent the image cache of extension icon by using the reloaded
339 // timestamp as a query string. The timestamp is recorded when the user 502 // timestamp as a query string. The timestamp is recorded when the user
340 // clicks the 'Reload' link. http://crbug.com/159302. 503 // clicks the 'Reload' link. http://crbug.com/159302.
341 if (extensionReloadedTimestamp[extension.id]) { 504 if (extensionReloadedTimestamp[extension.id]) {
342 item.style.backgroundImage = 505 item.style.backgroundImage =
343 'url(' + extension.icon + '?' + 506 'url(' + extension.icon + '?' +
344 extensionReloadedTimestamp[extension.id] + ')'; 507 extensionReloadedTimestamp[extension.id] + ')';
345 } else { 508 } else {
346 item.style.backgroundImage = 'url(' + extension.icon + ')'; 509 item.style.backgroundImage = 'url(' + extension.icon + ')';
347 } 510 }
348 511
349 this.setText_(node, '.extension-title', extension.name); 512 this.setText_(row, '.extension-title', extension.name);
350 this.setText_(node, '.extension-version', extension.version); 513 this.setText_(row, '.extension-version', extension.version);
351 this.setText_(node, '.location-text', extension.locationText); 514 this.setText_(row, '.location-text', extension.locationText);
352 this.setText_(node, '.blacklist-text', extension.blacklistText); 515 this.setText_(row, '.blacklist-text', extension.blacklistText);
353 this.setText_(node, '.extension-description', 516 this.setText_(row, '.extension-description', extension.description);
354 extension.description);
355 517
356 // The 'Show Browser Action' button. 518 // The 'Show Browser Action' button.
357 this.updateVisibility_(node, '.show-button', 519 this.updateVisibility_(row, '.show-button',
358 extension.enable_show_button); 520 isActive && extension.enable_show_button);
359 521
360 // The 'allow in incognito' checkbox. 522 // The 'allow in incognito' checkbox.
361 this.updateVisibility_(node, '.incognito-control', 523 this.updateVisibility_(row, '.incognito-control',
362 this.data_.incognitoAvailable, function(item) { 524 isActive && this.data_.incognitoAvailable,
525 function(item) {
363 var incognito = item.querySelector('input'); 526 var incognito = item.querySelector('input');
364 incognito.disabled = !extension.incognitoCanBeEnabled; 527 incognito.disabled = !extension.incognitoCanBeEnabled;
365 incognito.checked = extension.enabledIncognito; 528 incognito.checked = extension.enabledIncognito;
366 }); 529 });
367 530
368 // Hide butterBar if incognito is not enabled for the extension. 531 // Hide butterBar if incognito is not enabled for the extension.
369 var butterBar = node.querySelector('.butter-bar'); 532 var butterBar = row.querySelector('.butter-bar');
370 butterBar.hidden = butterBar.hidden || !extension.enabledIncognito; 533 butterBar.hidden = butterBar.hidden || !extension.enabledIncognito;
371 534
372 // The 'collect errors' checkbox. This should only be visible if the 535 // The 'collect errors' checkbox. This should only be visible if the
373 // error console is enabled - we can detect this by the existence of the 536 // error console is enabled - we can detect this by the existence of the
374 // |errorCollectionEnabled| property. 537 // |errorCollectionEnabled| property.
375 this.updateVisibility_(node, '.error-collection-control', 538 this.updateVisibility_(row, '.error-collection-control',
376 extension.wantsErrorCollection, function(item) { 539 isActive && extension.wantsErrorCollection,
540 function(item) {
377 item.querySelector('input').checked = extension.errorCollectionEnabled; 541 item.querySelector('input').checked = extension.errorCollectionEnabled;
378 }); 542 });
379 543
380 // The 'allow on all urls' checkbox. This should only be visible if 544 // The 'allow on all urls' checkbox. This should only be visible if
381 // active script restrictions are enabled. If they are not enabled, no 545 // active script restrictions are enabled. If they are not enabled, no
382 // extensions should want all urls. 546 // extensions should want all urls.
383 this.updateVisibility_(node, '.all-urls-control', extension.showAllUrls, 547 this.updateVisibility_(row, '.all-urls-control',
384 function(item) { 548 isActive && extension.showAllUrls, function(item) {
385 item.querySelector('input').checked = extension.allowAllUrls; 549 item.querySelector('input').checked = extension.allowAllUrls;
386 }); 550 });
387 551
388 // The 'allow file:// access' checkbox. 552 // The 'allow file:// access' checkbox.
389 this.updateVisibility_(node, '.file-access-control', 553 this.updateVisibility_(row, '.file-access-control',
390 extension.wantsFileAccess, function(item) { 554 isActive && extension.wantsFileAccess,
555 function(item) {
391 item.querySelector('input').checked = extension.allowFileAccess; 556 item.querySelector('input').checked = extension.allowFileAccess;
392 }); 557 });
393 558
394 // The 'Options' button or link, depending on its behaviour. 559 // The 'Options' button or link, depending on its behaviour.
395 var optionsEnabled = extension.enabled && !!extension.optionsUrl; 560 var optionsEnabled = extension.enabled && !!extension.optionsUrl;
396 this.updateVisibility_(node, '.options-link', optionsEnabled && 561 this.updateVisibility_(row, '.options-link', optionsEnabled &&
397 extension.optionsOpenInTab); 562 extension.optionsOpenInTab);
398 this.updateVisibility_(node, '.options-button', optionsEnabled && 563 this.updateVisibility_(row, '.options-button', optionsEnabled &&
399 !extension.optionsOpenInTab); 564 !extension.optionsOpenInTab);
400 565
401 // The 'View in Web Store/View Web Site' link. 566 // The 'View in Web Store/View Web Site' link.
402 var siteLinkEnabled = !!extension.homepageUrl && 567 var siteLinkEnabled = !!extension.homepageUrl &&
403 !extension.enableExtensionInfoDialog; 568 !extension.enableExtensionInfoDialog;
404 this.updateVisibility_(node, '.site-link', siteLinkEnabled, 569 this.updateVisibility_(row, '.site-link', siteLinkEnabled,
405 function(item) { 570 function(item) {
406 item.href = extension.homepageUrl; 571 item.href = extension.homepageUrl;
407 item.textContent = loadTimeData.getString( 572 item.textContent = loadTimeData.getString(
408 extension.homepageProvided ? 'extensionSettingsVisitWebsite' : 573 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
409 'extensionSettingsVisitWebStore'); 574 'extensionSettingsVisitWebStore');
410 }); 575 });
411 576
412 // The 'Reload' link. 577 // The 'Reload' link.
413 this.updateVisibility_(node, '.reload-link', extension.allow_reload); 578 this.updateVisibility_(row, '.reload-link', extension.allow_reload);
414 579
415 // The 'Launch' link. 580 // The 'Launch' link.
416 this.updateVisibility_(node, '.launch-link', extension.allow_reload && 581 this.updateVisibility_(row, '.launch-link', extension.allow_reload &&
417 extension.is_platform_app); 582 extension.is_platform_app);
418 583
419 // The 'Reload' terminated link. 584 // The 'Reload' terminated link.
420 var isTerminated = extension.terminated; 585 var isTerminated = extension.terminated;
421 this.updateVisibility_(node, '.terminated-reload-link', isTerminated); 586 this.updateVisibility_(row, '.terminated-reload-link', isTerminated);
422 587
423 // The 'Repair' corrupted link. 588 // The 'Repair' corrupted link.
424 var canRepair = !isTerminated && extension.corruptInstall && 589 var canRepair = !isTerminated && extension.corruptInstall &&
425 extension.isFromStore; 590 extension.isFromStore;
426 this.updateVisibility_(node, '.corrupted-repair-button', canRepair); 591 this.updateVisibility_(row, '.corrupted-repair-button', canRepair);
427 592
428 // The 'Enabled' checkbox. 593 // The 'Enabled' checkbox.
429 var isOK = !isTerminated && !canRepair; 594 var isOK = !isTerminated && !canRepair;
430 this.updateVisibility_(node, '.enable-checkbox', isOK, function(item) { 595 this.updateVisibility_(row, '.enable-checkbox', isOK, function(item) {
431 var enableCheckboxDisabled = extension.managedInstall || 596 var enableCheckboxDisabled = extension.managedInstall ||
432 extension.suspiciousInstall || 597 extension.suspiciousInstall ||
433 extension.corruptInstall || 598 extension.corruptInstall ||
434 extension.updateRequiredByPolicy || 599 extension.updateRequiredByPolicy ||
435 extension.dependentExtensions.length > 0; 600 extension.dependentExtensions.length > 0;
436 item.querySelector('input').disabled = enableCheckboxDisabled; 601 item.querySelector('input').disabled = enableCheckboxDisabled;
437 item.querySelector('input').checked = extension.enabled; 602 item.querySelector('input').checked = extension.enabled;
438 }); 603 });
439 604
440 // Button for extensions controlled by policy. 605 // Button for extensions controlled by policy.
441 var controlNode = node.querySelector('.enable-controls'); 606 var controlNode = row.querySelector('.enable-controls');
442 var indicator = 607 var indicator =
443 controlNode.querySelector('.controlled-extension-indicator'); 608 controlNode.querySelector('.controlled-extension-indicator');
444 var needsIndicator = isOK && extension.managedInstall; 609 var needsIndicator = isOK && extension.managedInstall;
445 610
446 if (needsIndicator && !indicator) { 611 if (needsIndicator && !indicator) {
447 indicator = new cr.ui.ControlledIndicator(); 612 indicator = new cr.ui.ControlledIndicator();
448 indicator.classList.add('controlled-extension-indicator'); 613 indicator.classList.add('controlled-extension-indicator');
449 indicator.setAttribute('controlled-by', 'policy'); 614 indicator.setAttribute('controlled-by', 'policy');
450 var textPolicy = extension.policyText || ''; 615 var textPolicy = extension.policyText || '';
451 indicator.setAttribute('textpolicy', textPolicy); 616 indicator.setAttribute('textpolicy', textPolicy);
452 indicator.image.setAttribute('aria-label', textPolicy); 617 indicator.image.setAttribute('aria-label', textPolicy);
453 controlNode.appendChild(indicator); 618 controlNode.appendChild(indicator);
619 indicator.querySelector('div').setAttribute('column-type',
620 'enterprise');
454 } else if (!needsIndicator && indicator) { 621 } else if (!needsIndicator && indicator) {
455 controlNode.removeChild(indicator); 622 controlNode.removeChild(indicator);
456 } 623 }
457 624
458 // Developer mode //////////////////////////////////////////////////////// 625 // Developer mode ////////////////////////////////////////////////////////
459 626
460 // First we have the id. 627 // First we have the id.
461 var idLabel = node.querySelector('.extension-id'); 628 var idLabel = row.querySelector('.extension-id');
462 idLabel.textContent = ' ' + extension.id; 629 idLabel.textContent = ' ' + extension.id;
463 630
464 // Then the path, if provided by unpacked extension. 631 // Then the path, if provided by unpacked extension.
465 this.updateVisibility_(node, '.load-path', extension.isUnpacked, 632 this.updateVisibility_(row, '.load-path', extension.isUnpacked,
466 function(item) { 633 function(item) {
467 item.querySelector('a:first-of-type').textContent = 634 item.querySelector('a:first-of-type').textContent =
468 ' ' + extension.prettifiedPath; 635 ' ' + extension.prettifiedPath;
469 }); 636 });
470 637
471 // Then the 'managed, cannot uninstall/disable' message. 638 // Then the 'managed, cannot uninstall/disable' message.
472 // We would like to hide managed installed message since this 639 // We would like to hide managed installed message since this
473 // extension is disabled. 640 // extension is disabled.
474 var isRequired = extension.managedInstall || extension.recommendedInstall; 641 var isRequired = extension.managedInstall || extension.recommendedInstall;
475 this.updateVisibility_(node, '.managed-message', isRequired && 642 this.updateVisibility_(row, '.managed-message', isRequired &&
476 !extension.updateRequiredByPolicy); 643 !extension.updateRequiredByPolicy);
477 644
478 // Then the 'This isn't from the webstore, looks suspicious' message. 645 // Then the 'This isn't from the webstore, looks suspicious' message.
479 this.updateVisibility_(node, '.suspicious-install-message', !isRequired && 646 this.updateVisibility_(row, '.suspicious-install-message', !isRequired &&
480 extension.suspiciousInstall); 647 extension.suspiciousInstall);
481 648
482 // Then the 'This is a corrupt extension' message. 649 // Then the 'This is a corrupt extension' message.
483 this.updateVisibility_(node, '.corrupt-install-message', !isRequired && 650 this.updateVisibility_(row, '.corrupt-install-message', !isRequired &&
484 extension.corruptInstall); 651 extension.corruptInstall);
485 652
486 // Then the 'An update required by enterprise policy' message. Note that 653 // Then the 'An update required by enterprise policy' message. Note that
487 // a force-installed extension might be disabled due to being outdated 654 // a force-installed extension might be disabled due to being outdated
488 // as well. 655 // as well.
489 this.updateVisibility_(node, '.update-required-message', 656 this.updateVisibility_(row, '.update-required-message',
490 extension.updateRequiredByPolicy); 657 extension.updateRequiredByPolicy);
491 658
492 // The 'following extensions depend on this extension' list. 659 // The 'following extensions depend on this extension' list.
493 var hasDependents = extension.dependentExtensions.length > 0; 660 var hasDependents = extension.dependentExtensions.length > 0;
494 node.classList.toggle('developer-extras', hasDependents); 661 row.classList.toggle('developer-extras', hasDependents);
495 this.updateVisibility_(node, '.dependent-extensions-message', 662 this.updateVisibility_(row, '.dependent-extensions-message',
496 hasDependents, function(item) { 663 hasDependents, function(item) {
497 var dependentList = item.querySelector('ul'); 664 var dependentList = item.querySelector('ul');
498 dependentList.textContent = ''; 665 dependentList.textContent = '';
499 var dependentTemplate = $('template-collection').querySelector( 666 var dependentTemplate = $('template-collection').querySelector(
500 '.dependent-list-item'); 667 '.dependent-list-item');
501 extension.dependentExtensions.forEach(function(elem) { 668 extension.dependentExtensions.forEach(function(elem) {
502 var depNode = dependentTemplate.cloneNode(true); 669 var depNode = dependentTemplate.cloneNode(true);
503 depNode.querySelector('.dep-extension-title').textContent = elem.name; 670 depNode.querySelector('.dep-extension-title').textContent = elem.name;
504 depNode.querySelector('.dep-extension-id').textContent = elem.id; 671 depNode.querySelector('.dep-extension-id').textContent = elem.id;
505 dependentList.appendChild(depNode); 672 dependentList.appendChild(depNode);
506 }); 673 });
507 }); 674 });
508 675
509 // The active views. 676 // The active views.
510 this.updateVisibility_(node, '.active-views', extension.views.length > 0, 677 this.updateVisibility_(row, '.active-views', extension.views.length > 0,
511 function(item) { 678 function(item) {
512 var link = item.querySelector('a'); 679 var link = item.querySelector('a');
513 680
514 // Link needs to be an only child before the list is updated. 681 // Link needs to be an only child before the list is updated.
515 while (link.nextElementSibling) 682 while (link.nextElementSibling)
516 item.removeChild(link.nextElementSibling); 683 item.removeChild(link.nextElementSibling);
517 684
518 // Link needs to be cleaned up if it was used before. 685 // Link needs to be cleaned up if it was used before.
519 link.textContent = ''; 686 link.textContent = '';
520 if (link.clickHandler) 687 if (link.clickHandler)
(...skipping 17 matching lines...) Expand all
538 view.incognito 705 view.incognito
539 ]); 706 ]);
540 }; 707 };
541 link.addEventListener('click', link.clickHandler); 708 link.addEventListener('click', link.clickHandler);
542 709
543 if (i < extension.views.length - 1) { 710 if (i < extension.views.length - 1) {
544 link = link.cloneNode(true); 711 link = link.cloneNode(true);
545 item.appendChild(link); 712 item.appendChild(link);
546 } 713 }
547 }); 714 });
715
716 var allLinks = item.querySelectorAll('a');
717 for (var i = 0; i < allLinks.length; ++i) {
718 allLinks[i].setAttribute('column-type', 'dev-activeViews' + i);
719 }
548 }); 720 });
549 721
550 // The extension warnings (describing runtime issues). 722 // The extension warnings (describing runtime issues).
551 this.updateVisibility_(node, '.extension-warnings', !!extension.warnings, 723 this.updateVisibility_(row, '.extension-warnings', !!extension.warnings,
552 function(item) { 724 function(item) {
553 var warningList = item.querySelector('ul'); 725 var warningList = item.querySelector('ul');
554 warningList.textContent = ''; 726 warningList.textContent = '';
555 extension.warnings.forEach(function(warning) { 727 extension.warnings.forEach(function(warning) {
556 var li = document.createElement('li'); 728 var li = document.createElement('li');
557 warningList.appendChild(li).innerText = warning; 729 warningList.appendChild(li).innerText = warning;
558 }); 730 });
559 }); 731 });
560 732
561 // If the ErrorConsole is enabled, we should have manifest and/or runtime 733 // If the ErrorConsole is enabled, we should have manifest and/or runtime
562 // errors. Otherwise, we may have install warnings. We should not have 734 // errors. Otherwise, we may have install warnings. We should not have
563 // both ErrorConsole errors and install warnings. 735 // both ErrorConsole errors and install warnings.
564 // Errors. 736 // Errors.
565 this.updateErrors_(node.querySelector('.manifest-errors'), 737 this.updateErrors_(row.querySelector('.manifest-errors'),
566 extension.manifestErrors); 738 'dev-manifestErrors', extension.manifestErrors);
567 this.updateErrors_(node.querySelector('.runtime-errors'), 739 this.updateErrors_(row.querySelector('.runtime-errors'),
568 extension.runtimeErrors); 740 'dev-runtimeErrors', extension.runtimeErrors);
569 741
570 // Install warnings. 742 // Install warnings.
571 this.updateVisibility_(node, '.install-warnings', 743 this.updateVisibility_(row, '.install-warnings',
572 !!extension.installWarnings, function(item) { 744 !!extension.installWarnings, function(item) {
573 var installWarningList = item.querySelector('ul'); 745 var installWarningList = item.querySelector('ul');
574 installWarningList.textContent = ''; 746 installWarningList.textContent = '';
575 if (extension.installWarnings) { 747 if (extension.installWarnings) {
576 extension.installWarnings.forEach(function(warning) { 748 extension.installWarnings.forEach(function(warning) {
577 var li = document.createElement('li'); 749 var li = document.createElement('li');
578 li.innerText = warning.message; 750 li.innerText = warning.message;
579 installWarningList.appendChild(li); 751 installWarningList.appendChild(li);
580 }); 752 });
581 } 753 }
582 }); 754 });
583 755
584 if (location.hash.substr(1) == extension.id) { 756 if (location.hash.substr(1) == extension.id) {
585 // Scroll beneath the fixed header so that the extension is not 757 // Scroll beneath the fixed header so that the extension is not
586 // obscured. 758 // obscured.
587 var topScroll = node.offsetTop - $('page-header').offsetHeight; 759 var topScroll = row.offsetTop - $('page-header').offsetHeight;
588 var pad = parseInt(window.getComputedStyle(node, null).marginTop, 10); 760 var pad = parseInt(window.getComputedStyle(row, null).marginTop, 10);
589 if (!isNaN(pad)) 761 if (!isNaN(pad))
590 topScroll -= pad / 2; 762 topScroll -= pad / 2;
591 setScrollTopForDocument(document, topScroll); 763 setScrollTopForDocument(document, topScroll);
592 } 764 }
765
766 row.updateFocusableElements();
767 this.focusGrid_.addRow(row);
593 }, 768 },
594 769
595 /** 770 /**
596 * Adds a listener to an element.
597 * @param {string} type The type of listener to add.
598 * @param {Element} node Ancestor of the element specified by |query|.
599 * @param {string} query A query to select an element in |node|.
600 * @param {function(Event)} handler The function that should be called
601 * on click.
602 * @private
603 */
604 addListener_: function(type, node, query, handler) {
605 node.querySelector(query).addEventListener(type, handler);
606 },
607
608 /**
609 * Updates an element's textContent. 771 * Updates an element's textContent.
610 * @param {Element} node Ancestor of the element specified by |query|. 772 * @param {Element} node Ancestor of the element specified by |query|.
611 * @param {string} query A query to select an element in |node|. 773 * @param {string} query A query to select an element in |node|.
612 * @param {string} textContent 774 * @param {string} textContent
613 * @private 775 * @private
614 */ 776 */
615 setText_: function(node, query, textContent) { 777 setText_: function(node, query, textContent) {
616 node.querySelector(query).textContent = textContent; 778 node.querySelector(query).textContent = textContent;
617 }, 779 },
618 780
(...skipping 11 matching lines...) Expand all
630 updateVisibility_: function(node, query, visible, opt_shownCallback) { 792 updateVisibility_: function(node, query, visible, opt_shownCallback) {
631 var item = assert(node.querySelector(query)); 793 var item = assert(node.querySelector(query));
632 item.hidden = !visible; 794 item.hidden = !visible;
633 if (visible && opt_shownCallback) 795 if (visible && opt_shownCallback)
634 opt_shownCallback(item); 796 opt_shownCallback(item);
635 }, 797 },
636 798
637 /** 799 /**
638 * Updates an element to show a list of errors. 800 * Updates an element to show a list of errors.
639 * @param {Element} panel An element to hold the errors. 801 * @param {Element} panel An element to hold the errors.
802 * @param {string} columnType A tag used to identify the column when
803 * changing focus.
640 * @param {Array<RuntimeError>|undefined} errors The errors to be displayed. 804 * @param {Array<RuntimeError>|undefined} errors The errors to be displayed.
641 * @private 805 * @private
642 */ 806 */
643 updateErrors_: function(panel, errors) { 807 updateErrors_: function(panel, columnType, errors) {
644 // TODO(hcarmona): Look into updating the ExtensionErrorList rather than 808 // TODO(hcarmona): Look into updating the ExtensionErrorList rather than
645 // rebuilding it every time. 809 // rebuilding it every time.
646 panel.hidden = !errors || errors.length == 0; 810 panel.hidden = !errors || errors.length == 0;
647 panel.textContent = ''; 811 panel.textContent = '';
648 if (!panel.hidden) { 812 if (!panel.hidden) {
649 panel.appendChild( 813 var errorList =
650 new extensions.ExtensionErrorList(assertInstanceof(errors, Array))); 814 new extensions.ExtensionErrorList(assertInstanceof(errors, Array));
815
816 panel.appendChild(errorList);
817
818 var list = errorList.getListElement();
819 if (list)
820 list.setAttribute('column-type', columnType + 'list');
821
822 var button = errorList.getToggleElement();
823 if (button)
824 button.setAttribute('column-type', columnType + 'button');
651 } 825 }
652 }, 826 },
653 827
654 /** 828 /**
655 * Opens the extension options overlay for the extension with the given id. 829 * Opens the extension options overlay for the extension with the given id.
656 * @param {string} extensionId The id of extension whose options page should 830 * @param {string} extensionId The id of extension whose options page should
657 * be displayed. 831 * be displayed.
658 * @param {boolean} scroll Whether the page should scroll to the extension 832 * @param {boolean} scroll Whether the page should scroll to the extension
659 * @private 833 * @private
660 */ 834 */
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
698 // TODO(dbeam): why do we need to focus <extensionoptions> before and 872 // TODO(dbeam): why do we need to focus <extensionoptions> before and
699 // after its showing animation? Makes very little sense to me. 873 // after its showing animation? Makes very little sense to me.
700 overlay.setInitialFocus(); 874 overlay.setInitialFocus();
701 }, 875 },
702 }; 876 };
703 877
704 return { 878 return {
705 ExtensionsList: ExtensionsList 879 ExtensionsList: ExtensionsList
706 }; 880 };
707 }); 881 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698