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

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

Powered by Google App Engine
This is Rietveld 408576698