cros: Remove default pinned apps trial.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extension_list.js
blobdbdf94b339721e0978ef7f46f24f27d682dcbb49
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
3 // found in the LICENSE file.
5 <include src="extension_error.js"></include>
7 cr.define('options', function() {
8   'use strict';
10   /**
11    * Creates a new list of extensions.
12    * @param {Object=} opt_propertyBag Optional properties.
13    * @constructor
14    * @extends {cr.ui.div}
15    */
16   var ExtensionsList = cr.ui.define('div');
18   /**
19    * @type {Object.<string, boolean>} A map from extension id to a boolean
20    *     indicating whether the incognito warning is showing. This persists
21    *     between calls to decorate.
22    */
23   var butterBarVisibility = {};
25   /**
26    * @type {Object.<string, string>} A map from extension id to last reloaded
27    *     timestamp. The timestamp is recorded when the user click the 'Reload'
28    *     link. It is used to refresh the icon of an unpacked extension.
29    *     This persists between calls to decorate.
30    */
31   var extensionReloadedTimestamp = {};
33   ExtensionsList.prototype = {
34     __proto__: HTMLDivElement.prototype,
36     /** @override */
37     decorate: function() {
38       this.textContent = '';
40       this.showExtensionNodes_();
41     },
43     getIdQueryParam_: function() {
44       return parseQueryParams(document.location)['id'];
45     },
47     /**
48      * Creates all extension items from scratch.
49      * @private
50      */
51     showExtensionNodes_: function() {
52       // Iterate over the extension data and add each item to the list.
53       this.data_.extensions.forEach(this.createNode_, this);
55       var idToHighlight = this.getIdQueryParam_();
56       if (idToHighlight && $(idToHighlight)) {
57         // Scroll offset should be calculated slightly higher than the actual
58         // offset of the element being scrolled to, so that it ends up not all
59         // the way at the top. That way it is clear that there are more elements
60         // above the element being scrolled to.
61         var scrollFudge = 1.2;
62         var scrollTop = $(idToHighlight).offsetTop - scrollFudge *
63             $(idToHighlight).clientHeight;
64         setScrollTopForDocument(document, scrollTop);
65       }
67       if (this.data_.extensions.length == 0)
68         this.classList.add('empty-extension-list');
69       else
70         this.classList.remove('empty-extension-list');
71     },
73     /**
74      * Synthesizes and initializes an HTML element for the extension metadata
75      * given in |extension|.
76      * @param {Object} extension A dictionary of extension metadata.
77      * @private
78      */
79     createNode_: function(extension) {
80       var template = $('template-collection').querySelector(
81           '.extension-list-item-wrapper');
82       var node = template.cloneNode(true);
83       node.id = extension.id;
85       if (!extension.enabled || extension.terminated)
86         node.classList.add('inactive-extension');
88       if (extension.managedInstall) {
89         node.classList.add('may-not-modify');
90         node.classList.add('may-not-remove');
91       } else if (extension.suspiciousInstall) {
92         node.classList.add('may-not-modify');
93       }
95       var idToHighlight = this.getIdQueryParam_();
96       if (node.id == idToHighlight)
97         node.classList.add('extension-highlight');
99       var item = node.querySelector('.extension-list-item');
100       // Prevent the image cache of extension icon by using the reloaded
101       // timestamp as a query string. The timestamp is recorded when the user
102       // clicks the 'Reload' link. http://crbug.com/159302.
103       if (extensionReloadedTimestamp[extension.id]) {
104         item.style.backgroundImage =
105             'url(' + extension.icon + '?' +
106             extensionReloadedTimestamp[extension.id] + ')';
107       } else {
108         item.style.backgroundImage = 'url(' + extension.icon + ')';
109       }
111       var title = node.querySelector('.extension-title');
112       title.textContent = extension.name;
114       var version = node.querySelector('.extension-version');
115       version.textContent = extension.version;
117       var locationText = node.querySelector('.location-text');
118       locationText.textContent = extension.locationText;
120       var description = node.querySelector('.extension-description span');
121       description.textContent = extension.description;
123       // The 'Show Browser Action' button.
124       if (extension.enable_show_button) {
125         var showButton = node.querySelector('.show-button');
126         showButton.addEventListener('click', function(e) {
127           chrome.send('extensionSettingsShowButton', [extension.id]);
128         });
129         showButton.hidden = false;
130       }
132       // The 'allow in incognito' checkbox.
133       var incognito = node.querySelector('.incognito-control input');
134       incognito.disabled = !extension.incognitoCanBeToggled;
135       incognito.checked = extension.enabledIncognito;
136       if (!incognito.disabled) {
137         incognito.addEventListener('change', function(e) {
138           var checked = e.target.checked;
139           butterBarVisibility[extension.id] = checked;
140           butterBar.hidden = !checked || extension.is_hosted_app;
141           chrome.send('extensionSettingsEnableIncognito',
142                       [extension.id, String(checked)]);
143         });
144       }
145       var butterBar = node.querySelector('.butter-bar');
146       butterBar.hidden = !butterBarVisibility[extension.id];
148       // The 'allow file:// access' checkbox.
149       if (extension.wantsFileAccess) {
150         var fileAccess = node.querySelector('.file-access-control');
151         fileAccess.addEventListener('click', function(e) {
152           chrome.send('extensionSettingsAllowFileAccess',
153                       [extension.id, String(e.target.checked)]);
154         });
155         fileAccess.querySelector('input').checked = extension.allowFileAccess;
156         fileAccess.hidden = false;
157       }
159       // The 'Options' link.
160       if (extension.enabled && extension.optionsUrl) {
161         var options = node.querySelector('.options-link');
162         options.addEventListener('click', function(e) {
163           chrome.send('extensionSettingsOptions', [extension.id]);
164           e.preventDefault();
165         });
166         options.hidden = false;
167       }
169       // The 'Permissions' link.
170       var permissions = node.querySelector('.permissions-link');
171       permissions.addEventListener('click', function(e) {
172         chrome.send('extensionSettingsPermissions', [extension.id]);
173         e.preventDefault();
174       });
176       // The 'View in Web Store/View Web Site' link.
177       if (extension.homepageUrl) {
178         var siteLink = node.querySelector('.site-link');
179         siteLink.href = extension.homepageUrl;
180         siteLink.textContent = loadTimeData.getString(
181                 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
182                                              'extensionSettingsVisitWebStore');
183         siteLink.hidden = false;
184       }
186       if (extension.allow_reload) {
187         // The 'Reload' link.
188         var reload = node.querySelector('.reload-link');
189         reload.addEventListener('click', function(e) {
190           chrome.send('extensionSettingsReload', [extension.id]);
191           extensionReloadedTimestamp[extension.id] = Date.now();
192         });
193         reload.hidden = false;
195         if (extension.is_platform_app) {
196           // The 'Launch' link.
197           var launch = node.querySelector('.launch-link');
198           launch.addEventListener('click', function(e) {
199             chrome.send('extensionSettingsLaunch', [extension.id]);
200           });
201           launch.hidden = false;
202         }
203       }
205       if (!extension.terminated) {
206         // The 'Enabled' checkbox.
207         var enable = node.querySelector('.enable-checkbox');
208         enable.hidden = false;
209         enable.querySelector('input').disabled = extension.managedInstall ||
210                                                  extension.suspiciousInstall;
212         if (!extension.managedInstall && !extension.suspiciousInstall) {
213           enable.addEventListener('click', function(e) {
214             // When e.target is the label instead of the checkbox, it doesn't
215             // have the checked property and the state of the checkbox is
216             // left unchanged.
217             var checked = e.target.checked;
218             if (checked == undefined)
219               checked = !e.currentTarget.querySelector('input').checked;
220             chrome.send('extensionSettingsEnable',
221                         [extension.id, checked ? 'true' : 'false']);
223             // This may seem counter-intuitive (to not set/clear the checkmark)
224             // but this page will be updated asynchronously if the extension
225             // becomes enabled/disabled. It also might not become enabled or
226             // disabled, because the user might e.g. get prompted when enabling
227             // and choose not to.
228             e.preventDefault();
229           });
230         }
232         enable.querySelector('input').checked = extension.enabled;
233       } else {
234         var terminatedReload = node.querySelector('.terminated-reload-link');
235         terminatedReload.hidden = false;
236         terminatedReload.addEventListener('click', function(e) {
237           chrome.send('extensionSettingsReload', [extension.id]);
238         });
239       }
241       // 'Remove' button.
242       var trashTemplate = $('template-collection').querySelector('.trash');
243       var trash = trashTemplate.cloneNode(true);
244       trash.title = loadTimeData.getString('extensionUninstall');
245       trash.addEventListener('click', function(e) {
246         butterBarVisibility[extension.id] = false;
247         chrome.send('extensionSettingsUninstall', [extension.id]);
248       });
249       node.querySelector('.enable-controls').appendChild(trash);
251       // Developer mode ////////////////////////////////////////////////////////
253       // First we have the id.
254       var idLabel = node.querySelector('.extension-id');
255       idLabel.textContent = ' ' + extension.id;
257       // Then the path, if provided by unpacked extension.
258       if (extension.isUnpacked) {
259         var loadPath = node.querySelector('.load-path');
260         loadPath.hidden = false;
261         loadPath.querySelector('span:nth-of-type(2)').textContent =
262             ' ' + extension.path;
263       }
265       // Then the 'managed, cannot uninstall/disable' message.
266       if (extension.managedInstall) {
267         node.querySelector('.managed-message').hidden = false;
268       } else if (extension.suspiciousInstall) {
269         // Then the 'This isn't from the webstore, looks suspicious' message.
270         node.querySelector('.suspicious-install-message').hidden = false;
271       }
273       // Then active views.
274       if (extension.views.length > 0) {
275         var activeViews = node.querySelector('.active-views');
276         activeViews.hidden = false;
277         var link = activeViews.querySelector('a');
279         extension.views.forEach(function(view, i) {
280           var displayName = view.generatedBackgroundPage ?
281               loadTimeData.getString('backgroundPage') : view.path;
282           var label = displayName +
283               (view.incognito ?
284                   ' ' + loadTimeData.getString('viewIncognito') : '') +
285               (view.renderProcessId == -1 ?
286                   ' ' + loadTimeData.getString('viewInactive') : '');
287           link.textContent = label;
288           link.addEventListener('click', function(e) {
289             // TODO(estade): remove conversion to string?
290             chrome.send('extensionSettingsInspect', [
291               String(extension.id),
292               String(view.renderProcessId),
293               String(view.renderViewId),
294               view.incognito
295             ]);
296           });
298           if (i < extension.views.length - 1) {
299             link = link.cloneNode(true);
300             activeViews.appendChild(link);
301           }
302         });
303       }
305       // The extension warnings (describing runtime issues).
306       if (extension.warnings) {
307         var panel = node.querySelector('.extension-warnings');
308         panel.hidden = false;
309         var list = panel.querySelector('ul');
310         extension.warnings.forEach(function(warning) {
311           list.appendChild(document.createElement('li')).innerText = warning;
312         });
313       }
315       // If the ErrorConsole is enabled, we should have manifest and/or runtime
316       // errors. Otherwise, we may have install warnings. We should not have
317       // both ErrorConsole errors and install warnings.
318       if (extension.manifestErrors) {
319         var panel = node.querySelector('.manifest-errors');
320         panel.hidden = false;
321         panel.appendChild(new extensions.ExtensionErrorList(
322             extension.manifestErrors, 'extensionErrorsManifestErrors'));
323       }
324       if (extension.runtimeErrors) {
325         var panel = node.querySelector('.runtime-errors');
326         panel.hidden = false;
327         panel.appendChild(new extensions.ExtensionErrorList(
328             extension.runtimeErrors, 'extensionErrorsRuntimeErrors'));
329       }
330       if (extension.installWarnings) {
331         var panel = node.querySelector('.install-warnings');
332         panel.hidden = false;
333         var list = panel.querySelector('ul');
334         extension.installWarnings.forEach(function(warning) {
335           var li = document.createElement('li');
336           li.innerText = warning.message;
337           list.appendChild(li);
338         });
339       }
341       this.appendChild(node);
342       if (location.hash.substr(1) == extension.id) {
343         // Scroll beneath the fixed header so that the extension is not
344         // obscured.
345         var topScroll = node.offsetTop - $('page-header').offsetHeight;
346         var pad = parseInt(getComputedStyle(node, null).marginTop, 10);
347         if (!isNaN(pad))
348           topScroll -= pad / 2;
349         setScrollTopForDocument(document, topScroll);
350       }
351     },
352   };
354   return {
355     ExtensionsList: ExtensionsList
356   };