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() {
11 * Creates a new list of extensions.
12 * @param {Object=} opt_propertyBag Optional properties.
14 * @extends {cr.ui.div}
16 var ExtensionsList = cr.ui.define('div');
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.
23 var butterBarVisibility = {};
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.
31 var extensionReloadedTimestamp = {};
33 ExtensionsList.prototype = {
34 __proto__: HTMLDivElement.prototype,
37 decorate: function() {
38 this.textContent = '';
40 this.showExtensionNodes_();
43 getIdQueryParam_: function() {
44 return parseQueryParams(document.location)['id'];
48 * Creates all extension items from scratch.
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 document.documentElement.scrollTop = $(idToHighlight).offsetTop -
63 scrollFudge * $(idToHighlight).clientHeight;
66 if (this.data_.extensions.length == 0)
67 this.classList.add('empty-extension-list');
69 this.classList.remove('empty-extension-list');
73 * Synthesizes and initializes an HTML element for the extension metadata
74 * given in |extension|.
75 * @param {Object} extension A dictionary of extension metadata.
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;
84 if (!extension.enabled || extension.terminated)
85 node.classList.add('inactive-extension');
87 if (!extension.userModifiable)
88 node.classList.add('may-not-disable');
90 var idToHighlight = this.getIdQueryParam_();
91 if (node.id == idToHighlight)
92 node.classList.add('extension-highlight');
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] + ')';
103 item.style.backgroundImage = 'url(' + extension.icon + ')';
106 var title = node.querySelector('.extension-title');
107 title.textContent = extension.name;
109 var version = node.querySelector('.extension-version');
110 version.textContent = extension.version;
112 var locationText = node.querySelector('.location-text');
113 locationText.textContent = extension.locationText;
115 var description = node.querySelector('.extension-description span');
116 description.textContent = extension.description;
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]);
124 showButton.hidden = false;
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)]);
140 var butterBar = node.querySelector('.butter-bar');
141 butterBar.hidden = !butterBarVisibility[extension.id];
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)]);
150 fileAccess.querySelector('input').checked = extension.allowFileAccess;
151 fileAccess.hidden = false;
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]);
161 options.hidden = false;
164 // The 'Permissions' link.
165 var permissions = node.querySelector('.permissions-link');
166 permissions.addEventListener('click', function(e) {
167 chrome.send('extensionSettingsPermissions', [extension.id]);
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;
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();
188 reload.hidden = false;
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]);
196 launch.hidden = false;
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;
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
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']);
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.
226 enable.querySelector('input').checked = extension.enabled;
228 var terminatedReload = node.querySelector('.terminated-reload-link');
229 terminatedReload.hidden = false;
230 terminatedReload.addEventListener('click', function(e) {
231 chrome.send('extensionSettingsReload', [extension.id]);
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]);
243 node.querySelector('.enable-controls').appendChild(trash);
245 // Developer mode ////////////////////////////////////////////////////////
247 // First we have the id.
248 var idLabel = node.querySelector('.extension-id');
249 idLabel.textContent = ' ' + extension.id;
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;
259 // Then the 'managed, cannot uninstall/disable' message.
260 if (!extension.userModifiable)
261 node.querySelector('.managed-message').hidden = false;
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');
269 extension.views.forEach(function(view, i) {
270 var displayName = view.generatedBackgroundPage ?
271 loadTimeData.getString('backgroundPage') : view.path;
272 var label = displayName +
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),
288 if (i < extension.views.length - 1) {
289 link = link.cloneNode(true);
290 activeViews.appendChild(link);
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;
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'));
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'));
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);
331 this.appendChild(node);
332 if (location.hash.substr(1) == extension.id) {
333 // Scroll beneath the fixed header so that the extension is not
335 var topScroll = node.offsetTop - $('page-header').offsetHeight;
336 var pad = parseInt(getComputedStyle(node, null).marginTop, 10);
338 topScroll -= pad / 2;
339 document.documentElement.scrollTop = topScroll;
345 ExtensionsList: ExtensionsList