- add sources.
[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"></include>
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         document.documentElement.scrollTop = $(idToHighlight).offsetTop -
63             scrollFudge * $(idToHighlight).clientHeight;
64       }
65
66       if (this.data_.extensions.length == 0)
67         this.classList.add('empty-extension-list');
68       else
69         this.classList.remove('empty-extension-list');
70     },
71
72     /**
73      * Synthesizes and initializes an HTML element for the extension metadata
74      * given in |extension|.
75      * @param {Object} extension A dictionary of extension metadata.
76      * @private
77      */
78     createNode_: function(extension) {
79       var template = $('template-collection').querySelector(
80           '.extension-list-item-wrapper');
81       var node = template.cloneNode(true);
82       node.id = extension.id;
83
84       if (!extension.enabled || extension.terminated)
85         node.classList.add('inactive-extension');
86
87       if (!extension.userModifiable)
88         node.classList.add('may-not-disable');
89
90       var idToHighlight = this.getIdQueryParam_();
91       if (node.id == idToHighlight)
92         node.classList.add('extension-highlight');
93
94       var item = node.querySelector('.extension-list-item');
95       // Prevent the image cache of extension icon by using the reloaded
96       // timestamp as a query string. The timestamp is recorded when the user
97       // clicks the 'Reload' link. http://crbug.com/159302.
98       if (extensionReloadedTimestamp[extension.id]) {
99         item.style.backgroundImage =
100             'url(' + extension.icon + '?' +
101             extensionReloadedTimestamp[extension.id] + ')';
102       } else {
103         item.style.backgroundImage = 'url(' + extension.icon + ')';
104       }
105
106       var title = node.querySelector('.extension-title');
107       title.textContent = extension.name;
108
109       var version = node.querySelector('.extension-version');
110       version.textContent = extension.version;
111
112       var locationText = node.querySelector('.location-text');
113       locationText.textContent = extension.locationText;
114
115       var description = node.querySelector('.extension-description span');
116       description.textContent = extension.description;
117
118       // The 'Show Browser Action' button.
119       if (extension.enable_show_button) {
120         var showButton = node.querySelector('.show-button');
121         showButton.addEventListener('click', function(e) {
122           chrome.send('extensionSettingsShowButton', [extension.id]);
123         });
124         showButton.hidden = false;
125       }
126
127       // The 'allow in incognito' checkbox.
128       var incognito = node.querySelector('.incognito-control input');
129       incognito.disabled = !extension.incognitoCanBeToggled;
130       incognito.checked = extension.enabledIncognito;
131       if (!incognito.disabled) {
132         incognito.addEventListener('change', function(e) {
133           var checked = e.target.checked;
134           butterBarVisibility[extension.id] = checked;
135           butterBar.hidden = !checked || extension.is_hosted_app;
136           chrome.send('extensionSettingsEnableIncognito',
137                       [extension.id, String(checked)]);
138         });
139       }
140       var butterBar = node.querySelector('.butter-bar');
141       butterBar.hidden = !butterBarVisibility[extension.id];
142
143       // The 'allow file:// access' checkbox.
144       if (extension.wantsFileAccess) {
145         var fileAccess = node.querySelector('.file-access-control');
146         fileAccess.addEventListener('click', function(e) {
147           chrome.send('extensionSettingsAllowFileAccess',
148                       [extension.id, String(e.target.checked)]);
149         });
150         fileAccess.querySelector('input').checked = extension.allowFileAccess;
151         fileAccess.hidden = false;
152       }
153
154       // The 'Options' link.
155       if (extension.enabled && extension.optionsUrl) {
156         var options = node.querySelector('.options-link');
157         options.addEventListener('click', function(e) {
158           chrome.send('extensionSettingsOptions', [extension.id]);
159           e.preventDefault();
160         });
161         options.hidden = false;
162       }
163
164       // The 'Permissions' link.
165       var permissions = node.querySelector('.permissions-link');
166       permissions.addEventListener('click', function(e) {
167         chrome.send('extensionSettingsPermissions', [extension.id]);
168         e.preventDefault();
169       });
170
171       // The 'View in Web Store/View Web Site' link.
172       if (extension.homepageUrl) {
173         var siteLink = node.querySelector('.site-link');
174         siteLink.href = extension.homepageUrl;
175         siteLink.textContent = loadTimeData.getString(
176                 extension.homepageProvided ? 'extensionSettingsVisitWebsite' :
177                                              'extensionSettingsVisitWebStore');
178         siteLink.hidden = false;
179       }
180
181       if (extension.allow_reload) {
182         // The 'Reload' link.
183         var reload = node.querySelector('.reload-link');
184         reload.addEventListener('click', function(e) {
185           chrome.send('extensionSettingsReload', [extension.id]);
186           extensionReloadedTimestamp[extension.id] = Date.now();
187         });
188         reload.hidden = false;
189
190         if (extension.is_platform_app) {
191           // The 'Launch' link.
192           var launch = node.querySelector('.launch-link');
193           launch.addEventListener('click', function(e) {
194             chrome.send('extensionSettingsLaunch', [extension.id]);
195           });
196           launch.hidden = false;
197         }
198       }
199
200       if (!extension.terminated) {
201         // The 'Enabled' checkbox.
202         var enable = node.querySelector('.enable-checkbox');
203         enable.hidden = false;
204         enable.querySelector('input').disabled = !extension.userModifiable;
205
206         if (extension.userModifiable) {
207           enable.addEventListener('click', function(e) {
208             // When e.target is the label instead of the checkbox, it doesn't
209             // have the checked property and the state of the checkbox is
210             // left unchanged.
211             var checked = e.target.checked;
212             if (checked == undefined)
213               checked = !e.currentTarget.querySelector('input').checked;
214             chrome.send('extensionSettingsEnable',
215                         [extension.id, checked ? 'true' : 'false']);
216
217             // This may seem counter-intuitive (to not set/clear the checkmark)
218             // but this page will be updated asynchronously if the extension
219             // becomes enabled/disabled. It also might not become enabled or
220             // disabled, because the user might e.g. get prompted when enabling
221             // and choose not to.
222             e.preventDefault();
223           });
224         }
225
226         enable.querySelector('input').checked = extension.enabled;
227       } else {
228         var terminatedReload = node.querySelector('.terminated-reload-link');
229         terminatedReload.hidden = false;
230         terminatedReload.addEventListener('click', function(e) {
231           chrome.send('extensionSettingsReload', [extension.id]);
232         });
233       }
234
235       // 'Remove' button.
236       var trashTemplate = $('template-collection').querySelector('.trash');
237       var trash = trashTemplate.cloneNode(true);
238       trash.title = loadTimeData.getString('extensionUninstall');
239       trash.addEventListener('click', function(e) {
240         butterBarVisibility[extension.id] = false;
241         chrome.send('extensionSettingsUninstall', [extension.id]);
242       });
243       node.querySelector('.enable-controls').appendChild(trash);
244
245       // Developer mode ////////////////////////////////////////////////////////
246
247       // First we have the id.
248       var idLabel = node.querySelector('.extension-id');
249       idLabel.textContent = ' ' + extension.id;
250
251       // Then the path, if provided by unpacked extension.
252       if (extension.isUnpacked) {
253         var loadPath = node.querySelector('.load-path');
254         loadPath.hidden = false;
255         loadPath.querySelector('span:nth-of-type(2)').textContent =
256             ' ' + extension.path;
257       }
258
259       // Then the 'managed, cannot uninstall/disable' message.
260       if (!extension.userModifiable)
261         node.querySelector('.managed-message').hidden = false;
262
263       // Then active views.
264       if (extension.views.length > 0) {
265         var activeViews = node.querySelector('.active-views');
266         activeViews.hidden = false;
267         var link = activeViews.querySelector('a');
268
269         extension.views.forEach(function(view, i) {
270           var displayName = view.generatedBackgroundPage ?
271               loadTimeData.getString('backgroundPage') : view.path;
272           var label = displayName +
273               (view.incognito ?
274                   ' ' + loadTimeData.getString('viewIncognito') : '') +
275               (view.renderProcessId == -1 ?
276                   ' ' + loadTimeData.getString('viewInactive') : '');
277           link.textContent = label;
278           link.addEventListener('click', function(e) {
279             // TODO(estade): remove conversion to string?
280             chrome.send('extensionSettingsInspect', [
281               String(extension.id),
282               String(view.renderProcessId),
283               String(view.renderViewId),
284               view.incognito
285             ]);
286           });
287
288           if (i < extension.views.length - 1) {
289             link = link.cloneNode(true);
290             activeViews.appendChild(link);
291           }
292         });
293       }
294
295       // The extension warnings (describing runtime issues).
296       if (extension.warnings) {
297         var panel = node.querySelector('.extension-warnings');
298         panel.hidden = false;
299         var list = panel.querySelector('ul');
300         extension.warnings.forEach(function(warning) {
301           list.appendChild(document.createElement('li')).innerText = warning;
302         });
303       }
304
305       // If the ErrorConsole is enabled, we should have manifest and/or runtime
306       // errors. Otherwise, we may have install warnings. We should not have
307       // both ErrorConsole errors and install warnings.
308       if (extension.manifestErrors) {
309         var panel = node.querySelector('.manifest-errors');
310         panel.hidden = false;
311         panel.appendChild(new extensions.ExtensionErrorList(
312             extension.manifestErrors, 'extensionErrorsManifestErrors'));
313       }
314       if (extension.runtimeErrors) {
315         var panel = node.querySelector('.runtime-errors');
316         panel.hidden = false;
317         panel.appendChild(new extensions.ExtensionErrorList(
318             extension.runtimeErrors, 'extensionErrorsRuntimeErrors'));
319       }
320       if (extension.installWarnings) {
321         var panel = node.querySelector('.install-warnings');
322         panel.hidden = false;
323         var list = panel.querySelector('ul');
324         extension.installWarnings.forEach(function(warning) {
325           var li = document.createElement('li');
326           li.innerText = warning.message;
327           list.appendChild(li);
328         });
329       }
330
331       this.appendChild(node);
332       if (location.hash.substr(1) == extension.id) {
333         // Scroll beneath the fixed header so that the extension is not
334         // obscured.
335         var topScroll = node.offsetTop - $('page-header').offsetHeight;
336         var pad = parseInt(getComputedStyle(node, null).marginTop, 10);
337         if (!isNaN(pad))
338           topScroll -= pad / 2;
339         document.documentElement.scrollTop = topScroll;
340       }
341     },
342   };
343
344   return {
345     ExtensionsList: ExtensionsList
346   };
347 });