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

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