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

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

Issue 893453002: Stop rebuilding all elements in chrome://extensions to preserve focus on refresh (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Remove unused line 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 59 matching lines...) Expand 10 before | Expand all | Expand 10 after
70 70
71 /** 71 /**
72 * Creates a new list of extensions. 72 * Creates a new list of extensions.
73 * @param {Object=} opt_propertyBag Optional properties. 73 * @param {Object=} opt_propertyBag Optional properties.
74 * @constructor 74 * @constructor
75 * @extends {HTMLDivElement} 75 * @extends {HTMLDivElement}
76 */ 76 */
77 var ExtensionsList = cr.ui.define('div'); 77 var ExtensionsList = cr.ui.define('div');
78 78
79 /** 79 /**
80 * @type {Object.<string, boolean>} A map from extension id to a boolean
81 * indicating whether the incognito warning is showing. This persists
82 * between calls to decorate.
83 */
84 var butterBarVisibility = {};
85
86 /**
87 * @type {Object.<string, number>} A map from extension id to last reloaded 80 * @type {Object.<string, number>} A map from extension id to last reloaded
88 * timestamp. The timestamp is recorded when the user click the 'Reload' 81 * timestamp. The timestamp is recorded when the user click the 'Reload'
89 * link. It is used to refresh the icon of an unpacked extension. 82 * link. It is used to refresh the icon of an unpacked extension.
90 * This persists between calls to decorate. 83 * This persists between calls to decorate.
91 */ 84 */
92 var extensionReloadedTimestamp = {}; 85 var extensionReloadedTimestamp = {};
93 86
94 ExtensionsList.prototype = { 87 ExtensionsList.prototype = {
95 __proto__: HTMLDivElement.prototype, 88 __proto__: HTMLDivElement.prototype,
96 89
97 /** 90 /**
98 * Indicates whether an embedded options page that was navigated to through 91 * Indicates whether an embedded options page that was navigated to through
99 * the '?options=' URL query has been shown to the user. This is necessary 92 * the '?options=' URL query has been shown to the user. This is necessary
100 * to prevent showExtensionNodes_ from opening the options more than once. 93 * to prevent showExtensionNodes_ from opening the options more than once.
101 * @type {boolean} 94 * @type {boolean}
102 * @private 95 * @private
103 */ 96 */
104 optionsShown_: false, 97 optionsShown_: false,
105 98
99 /**
100 * Necessary to only show the butterbar once.
101 * @private {boolean}
102 */
103 butterbarShown_: false,
104
106 decorate: function() { 105 decorate: function() {
107 this.textContent = '';
108
109 this.showExtensionNodes_(); 106 this.showExtensionNodes_();
110 }, 107 },
111 108
112 getIdQueryParam_: function() { 109 getIdQueryParam_: function() {
113 return parseQueryParams(document.location)['id']; 110 return parseQueryParams(document.location)['id'];
114 }, 111 },
115 112
116 getOptionsQueryParam_: function() { 113 getOptionsQueryParam_: function() {
117 return parseQueryParams(document.location)['options']; 114 return parseQueryParams(document.location)['options'];
118 }, 115 },
119 116
120 /** 117 /**
121 * Creates all extension items from scratch. 118 * Creates all extension items from scratch.
122 * @private 119 * @private
123 */ 120 */
124 showExtensionNodes_: function() { 121 showExtensionNodes_: function() {
122 // Any node that is not updated will be removed.
123 var seenIds = [];
124
125 // Iterate over the extension data and add each item to the list. 125 // Iterate over the extension data and add each item to the list.
126 this.data_.extensions.forEach(this.createNode_, this); 126 this.data_.extensions.forEach(function(extension) {
127 var node = $(extension.id);
128 seenIds.push(extension.id);
129
130 if (node)
131 this.updateNode_(extension, node);
132 else
133 this.createNode_(extension);
134 }, this);
135
136 // Remove extensions that are no longer installed.
137 var nodes = document.querySelectorAll('.extension-list-item-wrapper[id]');
138 for (var i = 0; i < nodes.length; ++i) {
139 var node = nodes[i];
140 if (seenIds.indexOf(node.id) < 0)
141 node.parentElement.removeChild(node);
142 }
127 143
128 var idToHighlight = this.getIdQueryParam_(); 144 var idToHighlight = this.getIdQueryParam_();
129 if (idToHighlight && $(idToHighlight)) 145 if (idToHighlight && $(idToHighlight))
130 this.scrollToNode_(idToHighlight); 146 this.scrollToNode_(idToHighlight);
131 147
132 var idToOpenOptions = this.getOptionsQueryParam_(); 148 var idToOpenOptions = this.getOptionsQueryParam_();
133 if (idToOpenOptions && $(idToOpenOptions)) 149 if (idToOpenOptions && $(idToOpenOptions))
134 this.showEmbeddedExtensionOptions_(idToOpenOptions, true); 150 this.showEmbeddedExtensionOptions_(idToOpenOptions, true);
135 151
136 var noExtensions = this.data_.extensions.length == 0; 152 var noExtensions = this.data_.extensions.length == 0;
(...skipping 21 matching lines...) Expand all
158 * given in |extension|. 174 * given in |extension|.
159 * @param {ExtensionData} extension A dictionary of extension metadata. 175 * @param {ExtensionData} extension A dictionary of extension metadata.
160 * @private 176 * @private
161 */ 177 */
162 createNode_: function(extension) { 178 createNode_: function(extension) {
163 var template = $('template-collection').querySelector( 179 var template = $('template-collection').querySelector(
164 '.extension-list-item-wrapper'); 180 '.extension-list-item-wrapper');
165 var node = template.cloneNode(true); 181 var node = template.cloneNode(true);
166 node.id = extension.id; 182 node.id = extension.id;
167 183
168 if (!extension.enabled || extension.terminated) 184 // The 'Show Browser Action' button.
169 node.classList.add('inactive-extension'); 185 this.addListener_('click', node, '.show-button', function(e) {
186 chrome.send('extensionSettingsShowButton', [extension.id]);
187 });
170 188
189 // The 'allow in incognito' checkbox.
190 this.addListener_('change', node, '.incognito-control input',
191 function(e) {
192 var butterBar = node.querySelector('.butter-bar');
193 var checked = e.target.checked;
194 if (!this.butterbarShown_) {
195 butterBar.hidden = !checked || extension.is_hosted_app;
196 this.butterbarShown_ = !butterBar.hidden;
197 } else
198 butterBar.hidden = true;
199 chrome.send('extensionSettingsEnableIncognito',
200 [extension.id, String(checked)]);
201 }.bind(this));
202
203 // The 'collect errors' checkbox. This should only be visible if the
204 // error console is enabled - we can detect this by the existence of the
205 // |errorCollectionEnabled| property.
206 this.addListener_('change', node, '.error-collection-control input',
207 function(e) {
208 chrome.send('extensionSettingsEnableErrorCollection',
209 [extension.id, String(e.target.checked)]);
210 });
211
212 // The 'allow on all urls' checkbox. This should only be visible if
213 // active script restrictions are enabled. If they are not enabled, no
214 // extensions should want all urls.
215 this.addListener_('click', node, '.all-urls-control', function(e) {
216 chrome.send('extensionSettingsAllowOnAllUrls',
217 [extension.id, String(e.target.checked)]);
218 });
219
220 // The 'allow file:// access' checkbox.
221 this.addListener_('click', node, '.file-access-control', function(e) {
222 chrome.send('extensionSettingsAllowFileAccess',
223 [extension.id, String(e.target.checked)]);
224 });
225
226 // The 'Options' button or link, depending on its behaviour.
227 // Set an href to get the correct mouse-over appearance (link,
228 // footer) - but the actual link opening is done through chrome.send
229 // with a preventDefault().
230 node.querySelector('.options-link').href = extension.optionsPageHref;
231 this.addListener_('click', node, '.options-link', function(e) {
232 chrome.send('extensionSettingsOptions', [extension.id]);
233 e.preventDefault();
234 });
235
236 this.addListener_('click', node, '.options-button', function(e) {
237 this.showEmbeddedExtensionOptions_(extension.id, false);
238 e.preventDefault();
239 }.bind(this));
240
241 // The 'Permissions' link.
242 this.addListener_('click', node, '.permissions-link', function(e) {
243 chrome.send('extensionSettingsPermissions', [extension.id]);
244 e.preventDefault();
245 });
246
247 // The 'Reload' link.
248 this.addListener_('click', node, '.reload-link', function(e) {
249 chrome.send('extensionSettingsReload', [extension.id]);
250 extensionReloadedTimestamp[extension.id] = Date.now();
251 });
252
253 // The 'Launch' link.
254 this.addListener_('click', node, '.launch-link', function(e) {
255 chrome.send('extensionSettingsLaunch', [extension.id]);
256 });
257
258 // The 'Reload' terminated link.
259 this.addListener_('click', node, '.terminated-reload-link', function(e) {
260 chrome.send('extensionSettingsReload', [extension.id]);
261 });
262
263 // The 'Repair' corrupted link.
264 this.addListener_('click', node, '.corrupted-repair-button', function(e) {
265 chrome.send('extensionSettingsRepair', [extension.id]);
266 });
267
268 // The 'Enabled' checkbox.
269 this.addListener_('change', node, '.enable-checkbox input', function(e) {
270 var checked = e.target.checked;
271 chrome.send('extensionSettingsEnable', [extension.id, String(checked)]);
272
273 // This may seem counter-intuitive (to not set/clear the checkmark)
274 // but this page will be updated asynchronously if the extension
275 // becomes enabled/disabled. It also might not become enabled or
276 // disabled, because the user might e.g. get prompted when enabling
277 // and choose not to.
278 e.preventDefault();
279 });
280
281 // 'Remove' button.
282 var trashTemplate = $('template-collection').querySelector('.trash');
283 var trash = trashTemplate.cloneNode(true);
284 trash.title = loadTimeData.getString('extensionUninstall');
285 trash.addEventListener('click', function(e) {
286 chrome.send('extensionSettingsUninstall', [extension.id]);
287 });
288 node.querySelector('.enable-controls').appendChild(trash);
289
290 // Developer mode ////////////////////////////////////////////////////////
291
292 // The path, if provided by unpacked extension.
293 this.addListener_('click', node, '.load-path a:first-of-type',
294 function(e) {
295 chrome.send('extensionSettingsShowPath', [String(extension.id)]);
296 e.preventDefault();
297 });
298
299 this.appendChild(node);
300 this.updateNode_(extension, node);
301 },
302
303 /**
304 * Updates an HTML element for the extension metadata given in |extension|.
305 * @param {ExtensionData} extension A dictionary of extension metadata.
306 * @param {Element} node The node that is being updated.
307 * @private
308 */
309 updateNode_: function(extension, node) {
310 node.classList.toggle('inactive-extension',
311 !extension.enabled || extension.terminated);
312
313 node.classList.remove('policy-controlled', 'may-not-modify',
314 'may-not-remove');
171 var classes = []; 315 var classes = [];
172 if (extension.managedInstall) { 316 if (extension.managedInstall) {
173 classes.push('policy-controlled', 'may-not-modify'); 317 classes.push('policy-controlled', 'may-not-modify');
174 } else if (extension.dependentExtensions.length > 0) { 318 } else if (extension.dependentExtensions.length > 0) {
175 classes.push('may-not-remove', 'may-not-modify'); 319 classes.push('may-not-remove', 'may-not-modify');
176 } else if (extension.recommendedInstall) { 320 } else if (extension.recommendedInstall) {
177 classes.push('may-not-remove'); 321 classes.push('may-not-remove');
178 } else if (extension.suspiciousInstall || 322 } else if (extension.suspiciousInstall ||
179 extension.corruptInstall || 323 extension.corruptInstall ||
180 extension.updateRequiredByPolicy) { 324 extension.updateRequiredByPolicy) {
181 classes.push('may-not-modify'); 325 classes.push('may-not-modify');
182 } 326 }
183 node.classList.add.apply(node.classList, classes); 327 node.classList.add.apply(node.classList, classes);
184 328
185 var idToHighlight = this.getIdQueryParam_(); 329 node.classList.toggle('extension-highlight',
186 if (node.id == idToHighlight) 330 node.id == this.getIdQueryParam_());
187 node.classList.add('extension-highlight');
188 331
189 var item = node.querySelector('.extension-list-item'); 332 var item = node.querySelector('.extension-list-item');
190 // Prevent the image cache of extension icon by using the reloaded 333 // Prevent the image cache of extension icon by using the reloaded
191 // timestamp as a query string. The timestamp is recorded when the user 334 // timestamp as a query string. The timestamp is recorded when the user
192 // clicks the 'Reload' link. http://crbug.com/159302. 335 // clicks the 'Reload' link. http://crbug.com/159302.
193 if (extensionReloadedTimestamp[extension.id]) { 336 if (extensionReloadedTimestamp[extension.id]) {
194 item.style.backgroundImage = 337 item.style.backgroundImage =
195 'url(' + extension.icon + '?' + 338 'url(' + extension.icon + '?' +
196 extensionReloadedTimestamp[extension.id] + ')'; 339 extensionReloadedTimestamp[extension.id] + ')';
197 } else { 340 } else {
198 item.style.backgroundImage = 'url(' + extension.icon + ')'; 341 item.style.backgroundImage = 'url(' + extension.icon + ')';
199 } 342 }
200 343
201 var title = node.querySelector('.extension-title'); 344 this.setText_(node, '.extension-title', extension.name);
202 title.textContent = extension.name; 345 this.setText_(node, '.extension-version', extension.version);
203 346 this.setText_(node, '.location-text', extension.locationText);
204 var version = node.querySelector('.extension-version'); 347 this.setText_(node, '.blacklist-text', extension.blacklistText);
205 version.textContent = extension.version; 348 this.setText_(node, '.extension-description',
206 349 extension.description);
207 var locationText = node.querySelector('.location-text');
208 locationText.textContent = extension.locationText;
209
210 var blacklistText = node.querySelector('.blacklist-text');
211 blacklistText.textContent = extension.blacklistText;
212
213 var description = document.createElement('span');
214 description.textContent = extension.description;
215 node.querySelector('.extension-description').appendChild(description);
216 350
217 // The 'Show Browser Action' button. 351 // The 'Show Browser Action' button.
218 if (extension.enable_show_button) { 352 this.updateVisibility_(node, '.show-button',
219 var showButton = node.querySelector('.show-button'); 353 extension.enable_show_button);
220 showButton.addEventListener('click', function(e) {
221 chrome.send('extensionSettingsShowButton', [extension.id]);
222 });
223 showButton.hidden = false;
224 }
225 354
226 // The 'allow in incognito' checkbox. 355 // The 'allow in incognito' checkbox.
227 node.querySelector('.incognito-control').hidden = 356 this.updateVisibility_(node, '.incognito-control',
228 !this.data_.incognitoAvailable; 357 this.data_.incognitoAvailable, function(item) {
229 var incognito = node.querySelector('.incognito-control input'); 358 var incognito = item.querySelector('input');
230 incognito.disabled = !extension.incognitoCanBeEnabled; 359 incognito.disabled = !extension.incognitoCanBeEnabled;
231 incognito.checked = extension.enabledIncognito; 360 incognito.checked = extension.enabledIncognito;
232 if (!incognito.disabled) { 361 });
233 incognito.addEventListener('change', function(e) { 362
234 var checked = e.target.checked; 363 // Hide butterBar if incognito is not enabled for the extension.
235 butterBarVisibility[extension.id] = checked;
236 butterBar.hidden = !checked || extension.is_hosted_app;
237 chrome.send('extensionSettingsEnableIncognito',
238 [extension.id, String(checked)]);
239 });
240 }
241 var butterBar = node.querySelector('.butter-bar'); 364 var butterBar = node.querySelector('.butter-bar');
242 butterBar.hidden = !butterBarVisibility[extension.id]; 365 butterBar.hidden = butterBar.hidden || !extension.enabledIncognito;
243 366
244 // The 'collect errors' checkbox. This should only be visible if the 367 // The 'collect errors' checkbox. This should only be visible if the
245 // error console is enabled - we can detect this by the existence of the 368 // error console is enabled - we can detect this by the existence of the
246 // |errorCollectionEnabled| property. 369 // |errorCollectionEnabled| property.
247 if (extension.wantsErrorCollection) { 370 this.updateVisibility_(node, '.error-collection-control',
248 node.querySelector('.error-collection-control').hidden = false; 371 extension.wantsErrorCollection, function(item) {
249 var errorCollection = 372 item.querySelector('input').checked = extension.errorCollectionEnabled;
250 node.querySelector('.error-collection-control input'); 373 });
251 errorCollection.checked = extension.errorCollectionEnabled;
252 errorCollection.addEventListener('change', function(e) {
253 chrome.send('extensionSettingsEnableErrorCollection',
254 [extension.id, String(e.target.checked)]);
255 });
256 }
257 374
258 // The 'allow on all urls' checkbox. This should only be visible if 375 // The 'allow on all urls' checkbox. This should only be visible if
259 // active script restrictions are enabled. If they are not enabled, no 376 // active script restrictions are enabled. If they are not enabled, no
260 // extensions should want all urls. 377 // extensions should want all urls.
261 if (extension.showAllUrls) { 378 this.updateVisibility_(node, '.all-urls-control', extension.showAllUrls,
262 var allUrls = node.querySelector('.all-urls-control'); 379 function(item) {
263 allUrls.addEventListener('click', function(e) { 380 item.querySelector('input').checked = extension.allowAllUrls;
264 chrome.send('extensionSettingsAllowOnAllUrls', 381 });
265 [extension.id, String(e.target.checked)]);
266 });
267 allUrls.querySelector('input').checked = extension.allowAllUrls;
268 allUrls.hidden = false;
269 }
270 382
271 // The 'allow file:// access' checkbox. 383 // The 'allow file:// access' checkbox.
272 if (extension.wantsFileAccess) { 384 this.updateVisibility_(node, '.file-access-control',
273 var fileAccess = node.querySelector('.file-access-control'); 385 extension.wantsFileAccess, function(item) {
274 fileAccess.addEventListener('click', function(e) { 386 item.querySelector('input').checked = extension.allowFileAccess;
275 chrome.send('extensionSettingsAllowFileAccess', 387 });
276 [extension.id, String(e.target.checked)]);
277 });
278 fileAccess.querySelector('input').checked = extension.allowFileAccess;
279 fileAccess.hidden = false;
280 }
281 388
282 // The 'Options' button or link, depending on its behaviour. 389 // The 'Options' button or link, depending on its behaviour.
283 if (extension.enabled && extension.optionsUrl) { 390 var optionsEnabled = extension.enabled && !!extension.optionsUrl;
284 var options, optionsClickListener; 391 this.updateVisibility_(node, '.options-link', optionsEnabled &&
285 if (extension.optionsOpenInTab) { 392 extension.optionsOpenInTab);
286 options = node.querySelector('.options-link'); 393 this.updateVisibility_(node, '.options-button', optionsEnabled &&
287 // Set an href to get the correct mouse-over appearance (link, 394 !extension.optionsOpenInTab);
288 // footer) - but the actual link opening is done through chrome.send
289 // with a preventDefault().
290 options.setAttribute('href', extension.optionsPageHref);
291 optionsClickListener = function() {
292 chrome.send('extensionSettingsOptions', [extension.id]);
293 };
294 } else {
295 options = node.querySelector('.options-button');
296 optionsClickListener = function() {
297 this.showEmbeddedExtensionOptions_(extension.id, false);
298 }.bind(this);
299 }
300 options.addEventListener('click', function(e) {
301 optionsClickListener();
302 e.preventDefault();
303 });
304 options.hidden = false;
305 }
306 395
307 // The 'Permissions' link. 396 // The 'View in Web Store/View Web Site' link.
308 var permissions = node.querySelector('.permissions-link'); 397 var siteLinkEnabled = !!extension.homepageUrl &&
309 permissions.addEventListener('click', function(e) { 398 !extension.enableExtensionInfoDialog;
310 chrome.send('extensionSettingsPermissions', [extension.id]); 399 this.updateVisibility_(node, '.site-link', siteLinkEnabled,
311 e.preventDefault(); 400 function(item) {
401 item.href = extension.homepageUrl;
402 item.textContent = loadTimeData.getString(
403 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
404 'extensionSettingsVisitWebStore');
312 }); 405 });
313 406
314 // The 'View in Web Store/View Web Site' link. 407 // The 'Reload' link.
315 if (extension.homepageUrl && !extension.enableExtensionInfoDialog) { 408 this.updateVisibility_(node, '.reload-link', extension.allow_reload);
316 var siteLink = node.querySelector('.site-link');
317 siteLink.href = extension.homepageUrl;
318 siteLink.textContent = loadTimeData.getString(
319 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
320 'extensionSettingsVisitWebStore');
321 siteLink.hidden = false;
322 }
323 409
324 if (extension.allow_reload) { 410 // The 'Launch' link.
325 // The 'Reload' link. 411 this.updateVisibility_(node, '.launch-link', extension.allow_reload &&
326 var reload = node.querySelector('.reload-link'); 412 extension.is_platform_app);
327 reload.addEventListener('click', function(e) {
328 chrome.send('extensionSettingsReload', [extension.id]);
329 extensionReloadedTimestamp[extension.id] = Date.now();
330 });
331 reload.hidden = false;
332 413
333 if (extension.is_platform_app) { 414 // The 'Reload' terminated link.
334 // The 'Launch' link. 415 var isTerminated = extension.terminated;
335 var launch = node.querySelector('.launch-link'); 416 this.updateVisibility_(node, '.terminated-reload-link', isTerminated);
336 launch.addEventListener('click', function(e) {
337 chrome.send('extensionSettingsLaunch', [extension.id]);
338 });
339 launch.hidden = false;
340 }
341 }
342 417
343 if (extension.terminated) { 418 // The 'Repair' corrupted link.
344 var terminatedReload = node.querySelector('.terminated-reload-link'); 419 var canRepair = !isTerminated && extension.corruptInstall &&
345 terminatedReload.hidden = false; 420 extension.isFromStore;
346 terminatedReload.onclick = function() { 421 this.updateVisibility_(node, '.corrupted-repair-button', canRepair);
347 chrome.send('extensionSettingsReload', [extension.id]); 422
348 }; 423 // The 'Enabled' checkbox.
349 } else if (extension.corruptInstall && extension.isFromStore) { 424 var isOK = !isTerminated && !canRepair;
350 var repair = node.querySelector('.corrupted-repair-button'); 425 this.updateVisibility_(node, '.enable-checkbox', isOK, function(item) {
351 repair.hidden = false;
352 repair.onclick = function() {
353 chrome.send('extensionSettingsRepair', [extension.id]);
354 };
355 } else {
356 // The 'Enabled' checkbox.
357 var enable = node.querySelector('.enable-checkbox');
358 enable.hidden = false;
359 var enableCheckboxDisabled = extension.managedInstall || 426 var enableCheckboxDisabled = extension.managedInstall ||
360 extension.suspiciousInstall || 427 extension.suspiciousInstall ||
361 extension.corruptInstall || 428 extension.corruptInstall ||
362 extension.updateRequiredByPolicy || 429 extension.updateRequiredByPolicy ||
363 extension.dependentExtensions.length > 0; 430 extension.dependentExtensions.length > 0;
364 enable.querySelector('input').disabled = enableCheckboxDisabled; 431 item.querySelector('input').disabled = enableCheckboxDisabled;
432 item.querySelector('input').checked = extension.enabled;
433 });
365 434
366 if (extension.managedInstall) { 435 // Button for extensions controlled by policy.
367 var indicator = new cr.ui.ControlledIndicator(); 436 var controlNode = node.querySelector('.enable-controls');
368 indicator.classList.add('controlled-extension-indicator'); 437 var indicator =
369 indicator.setAttribute('controlled-by', 'policy'); 438 controlNode.querySelector('.controlled-extension-indicator');
370 var textPolicy = extension.policyText || ''; 439 var needsIndicator = isOK && extension.managedInstall;
371 indicator.setAttribute('textpolicy', textPolicy);
372 indicator.image.setAttribute('aria-label', textPolicy);
373 node.querySelector('.enable-controls').appendChild(indicator);
374 }
375 440
376 if (!enableCheckboxDisabled) { 441 if (needsIndicator && !indicator) {
377 enable.addEventListener('click', function(e) { 442 indicator = new cr.ui.ControlledIndicator();
378 // When e.target is the label instead of the checkbox, it doesn't 443 indicator.classList.add('controlled-extension-indicator');
379 // have the checked property and the state of the checkbox is 444 indicator.setAttribute('controlled-by', 'policy');
380 // left unchanged. 445 var textPolicy = extension.policyText || '';
381 var checked = e.target.checked; 446 indicator.setAttribute('textpolicy', textPolicy);
382 if (checked == undefined) 447 indicator.image.setAttribute('aria-label', textPolicy);
383 checked = !e.currentTarget.querySelector('input').checked; 448 controlNode.appendChild(indicator);
384 chrome.send('extensionSettingsEnable', 449 } else if (!needsIndicator && indicator) {
385 [extension.id, checked ? 'true' : 'false']); 450 controlNode.removeChild(indicator);
386
387 // This may seem counter-intuitive (to not set/clear the checkmark)
388 // but this page will be updated asynchronously if the extension
389 // becomes enabled/disabled. It also might not become enabled or
390 // disabled, because the user might e.g. get prompted when enabling
391 // and choose not to.
392 e.preventDefault();
393 });
394 }
395
396 enable.querySelector('input').checked = extension.enabled;
397 } 451 }
398 452
399 // 'Remove' button.
400 var trashTemplate = $('template-collection').querySelector('.trash');
401 var trash = trashTemplate.cloneNode(true);
402 trash.title = loadTimeData.getString('extensionUninstall');
403 trash.addEventListener('click', function(e) {
404 butterBarVisibility[extension.id] = false;
405 chrome.send('extensionSettingsUninstall', [extension.id]);
406 });
407 node.querySelector('.enable-controls').appendChild(trash);
408
409 // Developer mode //////////////////////////////////////////////////////// 453 // Developer mode ////////////////////////////////////////////////////////
410 454
411 // First we have the id. 455 // First we have the id.
412 var idLabel = node.querySelector('.extension-id'); 456 var idLabel = node.querySelector('.extension-id');
413 idLabel.textContent = ' ' + extension.id; 457 idLabel.textContent = ' ' + extension.id;
414 458
415 // Then the path, if provided by unpacked extension. 459 // Then the path, if provided by unpacked extension.
416 if (extension.isUnpacked) { 460 this.updateVisibility_(node, '.load-path', extension.isUnpacked,
417 var loadPath = node.querySelector('.load-path'); 461 function(item) {
418 loadPath.hidden = false; 462 item.querySelector('a:first-of-type').textContent =
419 var pathLink = loadPath.querySelector('a:nth-of-type(1)'); 463 ' ' + extension.prettifiedPath;
420 pathLink.textContent = ' ' + extension.prettifiedPath; 464 });
421 pathLink.addEventListener('click', function(e) {
422 chrome.send('extensionSettingsShowPath', [String(extension.id)]);
423 e.preventDefault();
424 });
425 }
426 465
427 // Then the 'managed, cannot uninstall/disable' message. 466 // Then the 'managed, cannot uninstall/disable' message.
428 if (extension.managedInstall || extension.recommendedInstall) { 467 // We would like to hide managed installed message since this
429 node.querySelector('.managed-message').hidden = false; 468 // extension is disabled.
430 } else { 469 var isRequired = extension.managedInstall || extension.recommendedInstall;
431 if (extension.suspiciousInstall) { 470 this.updateVisibility_(node, '.managed-message', isRequired &&
432 // Then the 'This isn't from the webstore, looks suspicious' message. 471 !extension.updateRequiredByPolicy);
433 node.querySelector('.suspicious-install-message').hidden = false; 472
434 } 473 // Then the 'This isn't from the webstore, looks suspicious' message.
435 if (extension.corruptInstall) { 474 this.updateVisibility_(node, '.suspicious-install-message', !isRequired &&
436 // Then the 'This is a corrupt extension' message. 475 extension.suspiciousInstall);
437 node.querySelector('.corrupt-install-message').hidden = false; 476
438 } 477 // Then the 'This is a corrupt extension' message.
439 } 478 this.updateVisibility_(node, '.corrupt-install-message', !isRequired &&
479 extension.corruptInstall);
440 480
441 // Then the 'An update required by enterprise policy' message. Note that 481 // Then the 'An update required by enterprise policy' message. Note that
442 // a force-installed extension might be disabled due to being outdated 482 // a force-installed extension might be disabled due to being outdated
443 // as well. 483 // as well.
444 if (extension.updateRequiredByPolicy) { 484 this.updateVisibility_(node, '.update-required-message',
445 node.querySelector('.update-required-message').hidden = false; 485 extension.updateRequiredByPolicy);
446 // We would like to hide managed installed message since this
447 // extension is disabled.
448 node.querySelector('.managed-message').hidden = true;
449 }
450 486
451 if (extension.dependentExtensions.length > 0) { 487 // The 'following extensions depend on this extension' list.
452 node.classList.add('developer-extras'); 488 var hasDependents = extension.dependentExtensions.length > 0;
453 var dependentMessage = 489 node.classList.toggle('developer-extras', hasDependents);
454 node.querySelector('.dependent-extensions-message'); 490 this.updateVisibility_(node, '.dependent-extensions-message',
455 dependentMessage.hidden = false; 491 hasDependents, function(item) {
456 var dependentList = dependentMessage.querySelector('ul'); 492 var dependentList = item.querySelector('ul');
493 dependentList.textContent = '';
457 var dependentTemplate = $('template-collection').querySelector( 494 var dependentTemplate = $('template-collection').querySelector(
458 '.dependent-list-item'); 495 '.dependent-list-item');
459 extension.dependentExtensions.forEach(function(elem) { 496 extension.dependentExtensions.forEach(function(elem) {
460 var depNode = dependentTemplate.cloneNode(true); 497 var depNode = dependentTemplate.cloneNode(true);
461 depNode.querySelector('.dep-extension-title').textContent = elem.name; 498 depNode.querySelector('.dep-extension-title').textContent = elem.name;
462 depNode.querySelector('.dep-extension-id').textContent = elem.id; 499 depNode.querySelector('.dep-extension-id').textContent = elem.id;
463 dependentList.appendChild(depNode); 500 dependentList.appendChild(depNode);
464 }); 501 });
465 } 502 });
466 503
467 // Then active views. 504 // The active views.
468 if (extension.views.length > 0) { 505 this.updateVisibility_(node, '.active-views', extension.views.length > 0,
469 var activeViews = node.querySelector('.active-views'); 506 function(item) {
470 activeViews.hidden = false; 507 var link = item.querySelector('a');
471 var link = activeViews.querySelector('a'); 508
509 // Link needs to be an only child before the list is updated.
510 while (link.nextElementSibling)
511 item.removeChild(link.nextElementSibling);
512
513 // Link needs to be cleaned up if it was used before.
514 link.textContent = '';
515 if (link.clickHandler)
516 link.removeEventListener('click', link.clickHandler);
472 517
473 extension.views.forEach(function(view, i) { 518 extension.views.forEach(function(view, i) {
474 var displayName = view.generatedBackgroundPage ? 519 var displayName = view.generatedBackgroundPage ?
475 loadTimeData.getString('backgroundPage') : view.path; 520 loadTimeData.getString('backgroundPage') : view.path;
476 var label = displayName + 521 var label = displayName +
477 (view.incognito ? 522 (view.incognito ?
478 ' ' + loadTimeData.getString('viewIncognito') : '') + 523 ' ' + loadTimeData.getString('viewIncognito') : '') +
479 (view.renderProcessId == -1 ? 524 (view.renderProcessId == -1 ?
480 ' ' + loadTimeData.getString('viewInactive') : ''); 525 ' ' + loadTimeData.getString('viewInactive') : '');
481 link.textContent = label; 526 link.textContent = label;
482 link.addEventListener('click', function(e) { 527 link.clickHandler = function(e) {
483 // TODO(estade): remove conversion to string? 528 // TODO(estade): remove conversion to string?
484 chrome.send('extensionSettingsInspect', [ 529 chrome.send('extensionSettingsInspect', [
485 String(extension.id), 530 String(extension.id),
486 String(view.renderProcessId), 531 String(view.renderProcessId),
487 String(view.renderViewId), 532 String(view.renderViewId),
488 view.incognito 533 view.incognito
489 ]); 534 ]);
490 }); 535 };
536 link.addEventListener('click', link.clickHandler);
491 537
492 if (i < extension.views.length - 1) { 538 if (i < extension.views.length - 1) {
493 link = link.cloneNode(true); 539 link = link.cloneNode(true);
494 activeViews.appendChild(link); 540 item.appendChild(link);
495 } 541 }
496 }); 542 });
497 } 543 });
498 544
499 // The extension warnings (describing runtime issues). 545 // The extension warnings (describing runtime issues).
500 if (extension.warnings) { 546 this.updateVisibility_(node, '.extension-warnings', !!extension.warnings,
501 var panel = node.querySelector('.extension-warnings'); 547 function(item) {
502 panel.hidden = false; 548 var warningList = item.querySelector('ul');
503 var list = panel.querySelector('ul'); 549 warningList.textContent = '';
504 extension.warnings.forEach(function(warning) { 550 extension.warnings.forEach(function(warning) {
505 list.appendChild(document.createElement('li')).innerText = warning; 551 var li = document.createElement('li');
552 warningList.appendChild(li).innerText = warning;
506 }); 553 });
507 } 554 });
508 555
509 // If the ErrorConsole is enabled, we should have manifest and/or runtime 556 // If the ErrorConsole is enabled, we should have manifest and/or runtime
510 // errors. Otherwise, we may have install warnings. We should not have 557 // errors. Otherwise, we may have install warnings. We should not have
511 // both ErrorConsole errors and install warnings. 558 // both ErrorConsole errors and install warnings.
512 if (extension.manifestErrors) { 559 // Errors.
513 var panel = node.querySelector('.manifest-errors'); 560 this.updateErrors_(node.querySelector('.manifest-errors'),
514 panel.hidden = false; 561 extension.manifestErrors);
515 panel.appendChild(new extensions.ExtensionErrorList( 562 this.updateErrors_(node.querySelector('.runtime-errors'),
516 extension.manifestErrors)); 563 extension.runtimeErrors);
517 }
518 if (extension.runtimeErrors) {
519 var panel = node.querySelector('.runtime-errors');
520 panel.hidden = false;
521 panel.appendChild(new extensions.ExtensionErrorList(
522 extension.runtimeErrors));
523 }
524 if (extension.installWarnings) {
525 var panel = node.querySelector('.install-warnings');
526 panel.hidden = false;
527 var list = panel.querySelector('ul');
528 extension.installWarnings.forEach(function(warning) {
529 var li = document.createElement('li');
530 li.innerText = warning.message;
531 list.appendChild(li);
532 });
533 }
534 564
535 this.appendChild(node); 565 // Install warnings.
566 this.updateVisibility_(node, '.install-warnings',
567 !!extension.installWarnings, function(item) {
568 var installWarningList = item.querySelector('ul');
569 installWarningList.textContent = '';
570 if (extension.installWarnings) {
571 extension.installWarnings.forEach(function(warning) {
572 var li = document.createElement('li');
573 li.innerText = warning.message;
574 installWarningList.appendChild(li);
575 });
576 }
577 });
578
536 if (location.hash.substr(1) == extension.id) { 579 if (location.hash.substr(1) == extension.id) {
537 // Scroll beneath the fixed header so that the extension is not 580 // Scroll beneath the fixed header so that the extension is not
538 // obscured. 581 // obscured.
539 var topScroll = node.offsetTop - $('page-header').offsetHeight; 582 var topScroll = node.offsetTop - $('page-header').offsetHeight;
540 var pad = parseInt(window.getComputedStyle(node, null).marginTop, 10); 583 var pad = parseInt(window.getComputedStyle(node, null).marginTop, 10);
541 if (!isNaN(pad)) 584 if (!isNaN(pad))
542 topScroll -= pad / 2; 585 topScroll -= pad / 2;
543 setScrollTopForDocument(document, topScroll); 586 setScrollTopForDocument(document, topScroll);
544 } 587 }
545 }, 588 },
546 589
547 /** 590 /**
591 * Adds a listener to an element.
592 * @param {string} type The type of listener to add.
593 * @param {Element} node Ancestor of the element specified by |query|.
594 * @param {string} query A query to select an element in |node|.
595 * @param {function(Event)} handler The function that should be called
596 * on click.
597 * @private
598 */
599 addListener_: function(type, node, query, handler) {
600 node.querySelector(query).addEventListener(type, handler);
601 },
602
603 /**
604 * Updates an element's textContent.
605 * @param {Element} node Ancestor of the element specified by |query|.
606 * @param {string} query A query to select an element in |node|.
607 * @param {string} textContent
608 * @private
609 */
610 setText_: function(node, query, textContent) {
611 node.querySelector(query).textContent = textContent;
612 },
613
614 /**
615 * Updates an element's visibility and calls |shownCallback| if it is
616 * visible.
617 * @param {Element} node Ancestor of the element specified by |query|.
618 * @param {string} query A query to select an element in |node|.
619 * @param {boolean} visible Whether the element should be visible or not.
620 * @param {function(Element)=} opt_shownCallback Callback if the element is
621 * visible. The element passed in will be the element specified by
622 * |query|.
623 * @private
624 */
625 updateVisibility_: function(node, query, visible, opt_shownCallback) {
626 var item = assert(node.querySelector(query));
627 item.hidden = !visible;
628 if (visible && opt_shownCallback)
629 opt_shownCallback(item);
630 },
631
632 /**
633 * Updates an element to show a list of errors.
634 * @param {Element} panel An element to hold the errors.
635 * @param {Array<RuntimeError>|undefined} errors The errors to be displayed.
636 * @private
637 */
638 updateErrors_: function(panel, errors) {
639 // TODO(hcarmona): Look into updating the ExtensionErrorList rather than
640 // rebuilding it every time.
641 panel.hidden = !errors || errors.length == 0;
642 panel.textContent = '';
643 if (!panel.hidden) {
644 panel.appendChild(
645 new extensions.ExtensionErrorList(assertInstanceof(errors, Array)));
646 }
647 },
648
649 /**
548 * Opens the extension options overlay for the extension with the given id. 650 * Opens the extension options overlay for the extension with the given id.
549 * @param {string} extensionId The id of extension whose options page should 651 * @param {string} extensionId The id of extension whose options page should
550 * be displayed. 652 * be displayed.
551 * @param {boolean} scroll Whether the page should scroll to the extension 653 * @param {boolean} scroll Whether the page should scroll to the extension
552 * @private 654 * @private
553 */ 655 */
554 showEmbeddedExtensionOptions_: function(extensionId, scroll) { 656 showEmbeddedExtensionOptions_: function(extensionId, scroll) {
555 if (this.optionsShown_) 657 if (this.optionsShown_)
556 return; 658 return;
557 659
(...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after
591 // TODO(dbeam): why do we need to focus <extensionoptions> before and 693 // TODO(dbeam): why do we need to focus <extensionoptions> before and
592 // after its showing animation? Makes very little sense to me. 694 // after its showing animation? Makes very little sense to me.
593 overlay.setInitialFocus(); 695 overlay.setInitialFocus();
594 }, 696 },
595 }; 697 };
596 698
597 return { 699 return {
598 ExtensionsList: ExtensionsList 700 ExtensionsList: ExtensionsList
599 }; 701 };
600 }); 702 });
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698