Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / extensions / extension_list.js
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.
4
5 <include src="extension_error.js">
6
7 cr.define('options', function() {
8   'use strict';
9
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');
17
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 = {};
24
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 = {};
32
33   ExtensionsList.prototype = {
34     __proto__: HTMLDivElement.prototype,
35
36     /** @override */
37     decorate: function() {
38       this.textContent = '';
39
40       this.showExtensionNodes_();
41     },
42
43     getIdQueryParam_: function() {
44       return parseQueryParams(document.location)['id'];
45     },
46
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);
54
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       }
66
67       if (this.data_.extensions.length == 0)
68         this.classList.add('empty-extension-list');
69       else
70         this.classList.remove('empty-extension-list');
71     },
72
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;
84
85       if (!extension.enabled || extension.terminated)
86         node.classList.add('inactive-extension');
87
88       if (extension.managedInstall ||
89           extension.dependentExtensions.length > 0) {
90         node.classList.add('may-not-modify');
91         node.classList.add('may-not-remove');
92       } else if (extension.suspiciousInstall || extension.corruptInstall) {
93         node.classList.add('may-not-modify');
94       }
95
96       var idToHighlight = this.getIdQueryParam_();
97       if (node.id == idToHighlight)
98         node.classList.add('extension-highlight');
99
100       var item = node.querySelector('.extension-list-item');
101       // Prevent the image cache of extension icon by using the reloaded
102       // timestamp as a query string. The timestamp is recorded when the user
103       // clicks the 'Reload' link. http://crbug.com/159302.
104       if (extensionReloadedTimestamp[extension.id]) {
105         item.style.backgroundImage =
106             'url(' + extension.icon + '?' +
107             extensionReloadedTimestamp[extension.id] + ')';
108       } else {
109         item.style.backgroundImage = 'url(' + extension.icon + ')';
110       }
111
112       var title = node.querySelector('.extension-title');
113       title.textContent = extension.name;
114
115       var version = node.querySelector('.extension-version');
116       version.textContent = extension.version;
117
118       var locationText = node.querySelector('.location-text');
119       locationText.textContent = extension.locationText;
120
121       var blacklistText = node.querySelector('.blacklist-text');
122       blacklistText.textContent = extension.blacklistText;
123
124       var description = node.querySelector('.extension-description span');
125       description.textContent = extension.description;
126
127       // The 'Show Browser Action' button.
128       if (extension.enable_show_button) {
129         var showButton = node.querySelector('.show-button');
130         showButton.addEventListener('click', function(e) {
131           chrome.send('extensionSettingsShowButton', [extension.id]);
132         });
133         showButton.hidden = false;
134       }
135
136       // The 'allow in incognito' checkbox.
137       node.querySelector('.incognito-control').hidden =
138           !this.data_.incognitoAvailable;
139       var incognito = node.querySelector('.incognito-control input');
140       incognito.disabled = !extension.incognitoCanBeEnabled;
141       incognito.checked = extension.enabledIncognito;
142       if (!incognito.disabled) {
143         incognito.addEventListener('change', function(e) {
144           var checked = e.target.checked;
145           butterBarVisibility[extension.id] = checked;
146           butterBar.hidden = !checked || extension.is_hosted_app;
147           chrome.send('extensionSettingsEnableIncognito',
148                       [extension.id, String(checked)]);
149         });
150       }
151       var butterBar = node.querySelector('.butter-bar');
152       butterBar.hidden = !butterBarVisibility[extension.id];
153
154       // The 'collect errors' checkbox. This should only be visible if the
155       // error console is enabled - we can detect this by the existence of the
156       // |errorCollectionEnabled| property.
157       if (extension.wantsErrorCollection) {
158         node.querySelector('.error-collection-control').hidden = false;
159         var errorCollection =
160             node.querySelector('.error-collection-control input');
161         errorCollection.checked = extension.errorCollectionEnabled;
162         errorCollection.addEventListener('change', function(e) {
163           chrome.send('extensionSettingsEnableErrorCollection',
164                       [extension.id, String(e.target.checked)]);
165         });
166       }
167
168       // The 'allow on all urls' checkbox. This should only be visible if
169       // active script restrictions are enabled. If they are not enabled, no
170       // extensions should want all urls.
171       if (extension.wantsAllUrls) {
172         var allUrls = node.querySelector('.all-urls-control');
173         allUrls.addEventListener('click', function(e) {
174           chrome.send('extensionSettingsAllowOnAllUrls',
175                       [extension.id, String(e.target.checked)]);
176         });
177         allUrls.querySelector('input').checked = extension.allowAllUrls;
178         allUrls.hidden = false;
179       }
180
181       // The 'allow file:// access' checkbox.
182       if (extension.wantsFileAccess) {
183         var fileAccess = node.querySelector('.file-access-control');
184         fileAccess.addEventListener('click', function(e) {
185           chrome.send('extensionSettingsAllowFileAccess',
186                       [extension.id, String(e.target.checked)]);
187         });
188         fileAccess.querySelector('input').checked = extension.allowFileAccess;
189         fileAccess.hidden = false;
190       }
191
192       // The 'Options' link.
193       if (extension.enabled && extension.optionsUrl) {
194         var options = node.querySelector('.options-link');
195         options.addEventListener('click', function(e) {
196           chrome.send('extensionSettingsOptions', [extension.id]);
197           e.preventDefault();
198         });
199         options.hidden = false;
200       }
201
202       // The 'Permissions' link.
203       var permissions = node.querySelector('.permissions-link');
204       permissions.addEventListener('click', function(e) {
205         chrome.send('extensionSettingsPermissions', [extension.id]);
206         e.preventDefault();
207       });
208
209       // The 'View in Web Store/View Web Site' link.
210       if (extension.homepageUrl) {
211         var siteLink = node.querySelector('.site-link');
212         siteLink.href = extension.homepageUrl;
213         siteLink.textContent = loadTimeData.getString(
214                 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
215                                              'extensionSettingsVisitWebStore');
216         siteLink.hidden = false;
217       }
218
219       if (extension.allow_reload) {
220         // The 'Reload' link.
221         var reload = node.querySelector('.reload-link');
222         reload.addEventListener('click', function(e) {
223           chrome.send('extensionSettingsReload', [extension.id]);
224           extensionReloadedTimestamp[extension.id] = Date.now();
225         });
226         reload.hidden = false;
227
228         if (extension.is_platform_app) {
229           // The 'Launch' link.
230           var launch = node.querySelector('.launch-link');
231           launch.addEventListener('click', function(e) {
232             chrome.send('extensionSettingsLaunch', [extension.id]);
233           });
234           launch.hidden = false;
235         }
236       }
237
238       if (!extension.terminated) {
239         // The 'Enabled' checkbox.
240         var enable = node.querySelector('.enable-checkbox');
241         enable.hidden = false;
242         var enableCheckboxDisabled = extension.managedInstall ||
243                                      extension.suspiciousInstall ||
244                                      extension.corruptInstall ||
245                                      extension.dependentExtensions.length > 0;
246         enable.querySelector('input').disabled = enableCheckboxDisabled;
247
248         if (!enableCheckboxDisabled) {
249           enable.addEventListener('click', function(e) {
250             // When e.target is the label instead of the checkbox, it doesn't
251             // have the checked property and the state of the checkbox is
252             // left unchanged.
253             var checked = e.target.checked;
254             if (checked == undefined)
255               checked = !e.currentTarget.querySelector('input').checked;
256             chrome.send('extensionSettingsEnable',
257                         [extension.id, checked ? 'true' : 'false']);
258
259             // This may seem counter-intuitive (to not set/clear the checkmark)
260             // but this page will be updated asynchronously if the extension
261             // becomes enabled/disabled. It also might not become enabled or
262             // disabled, because the user might e.g. get prompted when enabling
263             // and choose not to.
264             e.preventDefault();
265           });
266         }
267
268         enable.querySelector('input').checked = extension.enabled;
269       } else {
270         var terminatedReload = node.querySelector('.terminated-reload-link');
271         terminatedReload.hidden = false;
272         terminatedReload.addEventListener('click', function(e) {
273           chrome.send('extensionSettingsReload', [extension.id]);
274         });
275       }
276
277       // 'Remove' button.
278       var trashTemplate = $('template-collection').querySelector('.trash');
279       var trash = trashTemplate.cloneNode(true);
280       trash.title = loadTimeData.getString('extensionUninstall');
281       trash.addEventListener('click', function(e) {
282         butterBarVisibility[extension.id] = false;
283         chrome.send('extensionSettingsUninstall', [extension.id]);
284       });
285       node.querySelector('.enable-controls').appendChild(trash);
286
287       // Developer mode ////////////////////////////////////////////////////////
288
289       // First we have the id.
290       var idLabel = node.querySelector('.extension-id');
291       idLabel.textContent = ' ' + extension.id;
292
293       // Then the path, if provided by unpacked extension.
294       if (extension.isUnpacked) {
295         var loadPath = node.querySelector('.load-path');
296         loadPath.hidden = false;
297         var pathLink = loadPath.querySelector('a:nth-of-type(1)');
298         pathLink.textContent = ' ' + extension.prettifiedPath;
299         pathLink.addEventListener('click', function(e) {
300           chrome.send('extensionSettingsShowPath', [String(extension.id)]);
301           e.preventDefault();
302         });
303       }
304
305       // Then the 'managed, cannot uninstall/disable' message.
306       if (extension.managedInstall) {
307         node.querySelector('.managed-message').hidden = false;
308       } else {
309         if (extension.suspiciousInstall) {
310           // Then the 'This isn't from the webstore, looks suspicious' message.
311           node.querySelector('.suspicious-install-message').hidden = false;
312         }
313         if (extension.corruptInstall) {
314           // Then the 'This is a corrupt extension' message.
315           node.querySelector('.corrupt-install-message').hidden = false;
316         }
317       }
318
319       if (extension.dependentExtensions.length > 0) {
320         var dependentMessage =
321             node.querySelector('.dependent-extensions-message');
322         dependentMessage.hidden = false;
323         var dependentList = dependentMessage.querySelector('ul');
324         var dependentTemplate = $('template-collection').querySelector(
325             '.dependent-list-item');
326         extension.dependentExtensions.forEach(function(elem) {
327           var depNode = dependentTemplate.cloneNode(true);
328           depNode.querySelector('.dep-extension-title').textContent = elem.name;
329           depNode.querySelector('.dep-extension-id').textContent = elem.id;
330           dependentList.appendChild(depNode);
331         });
332       }
333
334       // Then active views.
335       if (extension.views.length > 0) {
336         var activeViews = node.querySelector('.active-views');
337         activeViews.hidden = false;
338         var link = activeViews.querySelector('a');
339
340         extension.views.forEach(function(view, i) {
341           var displayName = view.generatedBackgroundPage ?
342               loadTimeData.getString('backgroundPage') : view.path;
343           var label = displayName +
344               (view.incognito ?
345                   ' ' + loadTimeData.getString('viewIncognito') : '') +
346               (view.renderProcessId == -1 ?
347                   ' ' + loadTimeData.getString('viewInactive') : '');
348           link.textContent = label;
349           link.addEventListener('click', function(e) {
350             // TODO(estade): remove conversion to string?
351             chrome.send('extensionSettingsInspect', [
352               String(extension.id),
353               String(view.renderProcessId),
354               String(view.renderViewId),
355               view.incognito
356             ]);
357           });
358
359           if (i < extension.views.length - 1) {
360             link = link.cloneNode(true);
361             activeViews.appendChild(link);
362           }
363         });
364       }
365
366       // The extension warnings (describing runtime issues).
367       if (extension.warnings) {
368         var panel = node.querySelector('.extension-warnings');
369         panel.hidden = false;
370         var list = panel.querySelector('ul');
371         extension.warnings.forEach(function(warning) {
372           list.appendChild(document.createElement('li')).innerText = warning;
373         });
374       }
375
376       // If the ErrorConsole is enabled, we should have manifest and/or runtime
377       // errors. Otherwise, we may have install warnings. We should not have
378       // both ErrorConsole errors and install warnings.
379       if (extension.manifestErrors) {
380         var panel = node.querySelector('.manifest-errors');
381         panel.hidden = false;
382         panel.appendChild(new extensions.ExtensionErrorList(
383             extension.manifestErrors));
384       }
385       if (extension.runtimeErrors) {
386         var panel = node.querySelector('.runtime-errors');
387         panel.hidden = false;
388         panel.appendChild(new extensions.ExtensionErrorList(
389             extension.runtimeErrors));
390       }
391       if (extension.installWarnings) {
392         var panel = node.querySelector('.install-warnings');
393         panel.hidden = false;
394         var list = panel.querySelector('ul');
395         extension.installWarnings.forEach(function(warning) {
396           var li = document.createElement('li');
397           li.innerText = warning.message;
398           list.appendChild(li);
399         });
400       }
401
402       this.appendChild(node);
403       if (location.hash.substr(1) == extension.id) {
404         // Scroll beneath the fixed header so that the extension is not
405         // obscured.
406         var topScroll = node.offsetTop - $('page-header').offsetHeight;
407         var pad = parseInt(getComputedStyle(node, null).marginTop, 10);
408         if (!isNaN(pad))
409           topScroll -= pad / 2;
410         setScrollTopForDocument(document, topScroll);
411       }
412     },
413   };
414
415   return {
416     ExtensionsList: ExtensionsList
417   };
418 });