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 var scrollTop
= $(idToHighlight
).offsetTop
- scrollFudge
*
63 $(idToHighlight
).clientHeight
;
64 setScrollTopForDocument(document
, scrollTop
);
67 if (this.data_
.extensions
.length
== 0)
68 this.classList
.add('empty-extension-list');
70 this.classList
.remove('empty-extension-list');
74 * Synthesizes and initializes an HTML element for the extension metadata
75 * given in |extension|.
76 * @param {Object} extension A dictionary of extension metadata.
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');
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
] + ')';
108 item
.style
.backgroundImage
= 'url(' + extension
.icon
+ ')';
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 blacklistText
= node
.querySelector('.blacklist-text');
121 blacklistText
.textContent
= extension
.blacklistText
;
123 var description
= node
.querySelector('.extension-description span');
124 description
.textContent
= extension
.description
;
126 // The 'Show Browser Action' button.
127 if (extension
.enable_show_button
) {
128 var showButton
= node
.querySelector('.show-button');
129 showButton
.addEventListener('click', function(e
) {
130 chrome
.send('extensionSettingsShowButton', [extension
.id
]);
132 showButton
.hidden
= false;
135 // The 'allow in incognito' checkbox.
136 var incognito
= node
.querySelector('.incognito-control input');
137 incognito
.disabled
= !extension
.incognitoCanBeToggled
;
138 incognito
.checked
= extension
.enabledIncognito
;
139 if (!incognito
.disabled
) {
140 incognito
.addEventListener('change', function(e
) {
141 var checked
= e
.target
.checked
;
142 butterBarVisibility
[extension
.id
] = checked
;
143 butterBar
.hidden
= !checked
|| extension
.is_hosted_app
;
144 chrome
.send('extensionSettingsEnableIncognito',
145 [extension
.id
, String(checked
)]);
148 var butterBar
= node
.querySelector('.butter-bar');
149 butterBar
.hidden
= !butterBarVisibility
[extension
.id
];
151 // The 'allow file:// access' checkbox.
152 if (extension
.wantsFileAccess
) {
153 var fileAccess
= node
.querySelector('.file-access-control');
154 fileAccess
.addEventListener('click', function(e
) {
155 chrome
.send('extensionSettingsAllowFileAccess',
156 [extension
.id
, String(e
.target
.checked
)]);
158 fileAccess
.querySelector('input').checked
= extension
.allowFileAccess
;
159 fileAccess
.hidden
= false;
162 // The 'Options' link.
163 if (extension
.enabled
&& extension
.optionsUrl
) {
164 var options
= node
.querySelector('.options-link');
165 options
.addEventListener('click', function(e
) {
166 chrome
.send('extensionSettingsOptions', [extension
.id
]);
169 options
.hidden
= false;
172 // The 'Permissions' link.
173 var permissions
= node
.querySelector('.permissions-link');
174 permissions
.addEventListener('click', function(e
) {
175 chrome
.send('extensionSettingsPermissions', [extension
.id
]);
179 // The 'View in Web Store/View Web Site' link.
180 if (extension
.homepageUrl
) {
181 var siteLink
= node
.querySelector('.site-link');
182 siteLink
.href
= extension
.homepageUrl
;
183 siteLink
.textContent
= loadTimeData
.getString(
184 extension
.homepageProvided
? 'extensionSettingsVisitWebsite' :
185 'extensionSettingsVisitWebStore');
186 siteLink
.hidden
= false;
189 if (extension
.allow_reload
) {
190 // The 'Reload' link.
191 var reload
= node
.querySelector('.reload-link');
192 reload
.addEventListener('click', function(e
) {
193 chrome
.send('extensionSettingsReload', [extension
.id
]);
194 extensionReloadedTimestamp
[extension
.id
] = Date
.now();
196 reload
.hidden
= false;
198 if (extension
.is_platform_app
) {
199 // The 'Launch' link.
200 var launch
= node
.querySelector('.launch-link');
201 launch
.addEventListener('click', function(e
) {
202 chrome
.send('extensionSettingsLaunch', [extension
.id
]);
204 launch
.hidden
= false;
208 if (!extension
.terminated
) {
209 // The 'Enabled' checkbox.
210 var enable
= node
.querySelector('.enable-checkbox');
211 enable
.hidden
= false;
212 enable
.querySelector('input').disabled
= extension
.managedInstall
||
213 extension
.suspiciousInstall
;
215 if (!extension
.managedInstall
&& !extension
.suspiciousInstall
) {
216 enable
.addEventListener('click', function(e
) {
217 // When e.target is the label instead of the checkbox, it doesn't
218 // have the checked property and the state of the checkbox is
220 var checked
= e
.target
.checked
;
221 if (checked
== undefined)
222 checked
= !e
.currentTarget
.querySelector('input').checked
;
223 chrome
.send('extensionSettingsEnable',
224 [extension
.id
, checked
? 'true' : 'false']);
226 // This may seem counter-intuitive (to not set/clear the checkmark)
227 // but this page will be updated asynchronously if the extension
228 // becomes enabled/disabled. It also might not become enabled or
229 // disabled, because the user might e.g. get prompted when enabling
230 // and choose not to.
235 enable
.querySelector('input').checked
= extension
.enabled
;
237 var terminatedReload
= node
.querySelector('.terminated-reload-link');
238 terminatedReload
.hidden
= false;
239 terminatedReload
.addEventListener('click', function(e
) {
240 chrome
.send('extensionSettingsReload', [extension
.id
]);
245 var trashTemplate
= $('template-collection').querySelector('.trash');
246 var trash
= trashTemplate
.cloneNode(true);
247 trash
.title
= loadTimeData
.getString('extensionUninstall');
248 trash
.addEventListener('click', function(e
) {
249 butterBarVisibility
[extension
.id
] = false;
250 chrome
.send('extensionSettingsUninstall', [extension
.id
]);
252 node
.querySelector('.enable-controls').appendChild(trash
);
254 // Developer mode ////////////////////////////////////////////////////////
256 // First we have the id.
257 var idLabel
= node
.querySelector('.extension-id');
258 idLabel
.textContent
= ' ' + extension
.id
;
260 // Then the path, if provided by unpacked extension.
261 if (extension
.isUnpacked
) {
262 var loadPath
= node
.querySelector('.load-path');
263 loadPath
.hidden
= false;
264 loadPath
.querySelector('span:nth-of-type(2)').textContent
=
265 ' ' + extension
.path
;
268 // Then the 'managed, cannot uninstall/disable' message.
269 if (extension
.managedInstall
) {
270 node
.querySelector('.managed-message').hidden
= false;
271 } else if (extension
.suspiciousInstall
) {
272 // Then the 'This isn't from the webstore, looks suspicious' message.
273 node
.querySelector('.suspicious-install-message').hidden
= false;
276 // Then active views.
277 if (extension
.views
.length
> 0) {
278 var activeViews
= node
.querySelector('.active-views');
279 activeViews
.hidden
= false;
280 var link
= activeViews
.querySelector('a');
282 extension
.views
.forEach(function(view
, i
) {
283 var displayName
= view
.generatedBackgroundPage
?
284 loadTimeData
.getString('backgroundPage') : view
.path
;
285 var label
= displayName
+
287 ' ' + loadTimeData
.getString('viewIncognito') : '') +
288 (view
.renderProcessId
== -1 ?
289 ' ' + loadTimeData
.getString('viewInactive') : '');
290 link
.textContent
= label
;
291 link
.addEventListener('click', function(e
) {
292 // TODO(estade): remove conversion to string?
293 chrome
.send('extensionSettingsInspect', [
294 String(extension
.id
),
295 String(view
.renderProcessId
),
296 String(view
.renderViewId
),
301 if (i
< extension
.views
.length
- 1) {
302 link
= link
.cloneNode(true);
303 activeViews
.appendChild(link
);
308 // The extension warnings (describing runtime issues).
309 if (extension
.warnings
) {
310 var panel
= node
.querySelector('.extension-warnings');
311 panel
.hidden
= false;
312 var list
= panel
.querySelector('ul');
313 extension
.warnings
.forEach(function(warning
) {
314 list
.appendChild(document
.createElement('li')).innerText
= warning
;
318 // If the ErrorConsole is enabled, we should have manifest and/or runtime
319 // errors. Otherwise, we may have install warnings. We should not have
320 // both ErrorConsole errors and install warnings.
321 if (extension
.manifestErrors
) {
322 var panel
= node
.querySelector('.manifest-errors');
323 panel
.hidden
= false;
324 panel
.appendChild(new extensions
.ExtensionErrorList(
325 extension
.manifestErrors
));
327 if (extension
.runtimeErrors
) {
328 var panel
= node
.querySelector('.runtime-errors');
329 panel
.hidden
= false;
330 panel
.appendChild(new extensions
.ExtensionErrorList(
331 extension
.runtimeErrors
));
333 if (extension
.installWarnings
) {
334 var panel
= node
.querySelector('.install-warnings');
335 panel
.hidden
= false;
336 var list
= panel
.querySelector('ul');
337 extension
.installWarnings
.forEach(function(warning
) {
338 var li
= document
.createElement('li');
339 li
.innerText
= warning
.message
;
340 list
.appendChild(li
);
344 this.appendChild(node
);
345 if (location
.hash
.substr(1) == extension
.id
) {
346 // Scroll beneath the fixed header so that the extension is not
348 var topScroll
= node
.offsetTop
- $('page-header').offsetHeight
;
349 var pad
= parseInt(getComputedStyle(node
, null).marginTop
, 10);
351 topScroll
-= pad
/ 2;
352 setScrollTopForDocument(document
, topScroll
);
358 ExtensionsList
: ExtensionsList