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
|| extension
.corruptInstall
) {
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
.incognitoCanBeEnabled
;
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 'collect errors' checkbox. This should only be visible if the
152 // error console is enabled - we can detect this by the existence of the
153 // |errorCollectionEnabled| property.
154 if (extension
.wantsErrorCollection
) {
155 node
.querySelector('.error-collection-control').hidden
= false;
156 var errorCollection
=
157 node
.querySelector('.error-collection-control input');
158 errorCollection
.checked
= extension
.errorCollectionEnabled
;
159 errorCollection
.addEventListener('change', function(e
) {
160 chrome
.send('extensionSettingsEnableErrorCollection',
161 [extension
.id
, String(e
.target
.checked
)]);
165 // The 'allow file:// access' checkbox.
166 if (extension
.wantsFileAccess
) {
167 var fileAccess
= node
.querySelector('.file-access-control');
168 fileAccess
.addEventListener('click', function(e
) {
169 chrome
.send('extensionSettingsAllowFileAccess',
170 [extension
.id
, String(e
.target
.checked
)]);
172 fileAccess
.querySelector('input').checked
= extension
.allowFileAccess
;
173 fileAccess
.hidden
= false;
176 // The 'Options' link.
177 if (extension
.enabled
&& extension
.optionsUrl
) {
178 var options
= node
.querySelector('.options-link');
179 options
.addEventListener('click', function(e
) {
180 chrome
.send('extensionSettingsOptions', [extension
.id
]);
183 options
.hidden
= false;
186 // The 'Permissions' link.
187 var permissions
= node
.querySelector('.permissions-link');
188 permissions
.addEventListener('click', function(e
) {
189 chrome
.send('extensionSettingsPermissions', [extension
.id
]);
193 // The 'View in Web Store/View Web Site' link.
194 if (extension
.homepageUrl
) {
195 var siteLink
= node
.querySelector('.site-link');
196 siteLink
.href
= extension
.homepageUrl
;
197 siteLink
.textContent
= loadTimeData
.getString(
198 extension
.homepageProvided
? 'extensionSettingsVisitWebsite' :
199 'extensionSettingsVisitWebStore');
200 siteLink
.hidden
= false;
203 if (extension
.allow_reload
) {
204 // The 'Reload' link.
205 var reload
= node
.querySelector('.reload-link');
206 reload
.addEventListener('click', function(e
) {
207 chrome
.send('extensionSettingsReload', [extension
.id
]);
208 extensionReloadedTimestamp
[extension
.id
] = Date
.now();
210 reload
.hidden
= false;
212 if (extension
.is_platform_app
) {
213 // The 'Launch' link.
214 var launch
= node
.querySelector('.launch-link');
215 launch
.addEventListener('click', function(e
) {
216 chrome
.send('extensionSettingsLaunch', [extension
.id
]);
218 launch
.hidden
= false;
222 if (!extension
.terminated
) {
223 // The 'Enabled' checkbox.
224 var enable
= node
.querySelector('.enable-checkbox');
225 enable
.hidden
= false;
226 var managedOrHosedExtension
= extension
.managedInstall
||
227 extension
.suspiciousInstall
||
228 extension
.corruptInstall
;
229 enable
.querySelector('input').disabled
= managedOrHosedExtension
;
231 if (!managedOrHosedExtension
) {
232 enable
.addEventListener('click', function(e
) {
233 // When e.target is the label instead of the checkbox, it doesn't
234 // have the checked property and the state of the checkbox is
236 var checked
= e
.target
.checked
;
237 if (checked
== undefined)
238 checked
= !e
.currentTarget
.querySelector('input').checked
;
239 chrome
.send('extensionSettingsEnable',
240 [extension
.id
, checked
? 'true' : 'false']);
242 // This may seem counter-intuitive (to not set/clear the checkmark)
243 // but this page will be updated asynchronously if the extension
244 // becomes enabled/disabled. It also might not become enabled or
245 // disabled, because the user might e.g. get prompted when enabling
246 // and choose not to.
251 enable
.querySelector('input').checked
= extension
.enabled
;
253 var terminatedReload
= node
.querySelector('.terminated-reload-link');
254 terminatedReload
.hidden
= false;
255 terminatedReload
.addEventListener('click', function(e
) {
256 chrome
.send('extensionSettingsReload', [extension
.id
]);
261 var trashTemplate
= $('template-collection').querySelector('.trash');
262 var trash
= trashTemplate
.cloneNode(true);
263 trash
.title
= loadTimeData
.getString('extensionUninstall');
264 trash
.addEventListener('click', function(e
) {
265 butterBarVisibility
[extension
.id
] = false;
266 chrome
.send('extensionSettingsUninstall', [extension
.id
]);
268 node
.querySelector('.enable-controls').appendChild(trash
);
270 // Developer mode ////////////////////////////////////////////////////////
272 // First we have the id.
273 var idLabel
= node
.querySelector('.extension-id');
274 idLabel
.textContent
= ' ' + extension
.id
;
276 // Then the path, if provided by unpacked extension.
277 if (extension
.isUnpacked
) {
278 var loadPath
= node
.querySelector('.load-path');
279 loadPath
.hidden
= false;
280 loadPath
.querySelector('span:nth-of-type(2)').textContent
=
281 ' ' + extension
.path
;
284 // Then the 'managed, cannot uninstall/disable' message.
285 if (extension
.managedInstall
) {
286 node
.querySelector('.managed-message').hidden
= false;
288 if (extension
.suspiciousInstall
) {
289 // Then the 'This isn't from the webstore, looks suspicious' message.
290 node
.querySelector('.suspicious-install-message').hidden
= false;
292 if (extension
.corruptInstall
) {
293 // Then the 'This is a corrupt extension' message.
294 node
.querySelector('.corrupt-install-message').hidden
= false;
298 // Then active views.
299 if (extension
.views
.length
> 0) {
300 var activeViews
= node
.querySelector('.active-views');
301 activeViews
.hidden
= false;
302 var link
= activeViews
.querySelector('a');
304 extension
.views
.forEach(function(view
, i
) {
305 var displayName
= view
.generatedBackgroundPage
?
306 loadTimeData
.getString('backgroundPage') : view
.path
;
307 var label
= displayName
+
309 ' ' + loadTimeData
.getString('viewIncognito') : '') +
310 (view
.renderProcessId
== -1 ?
311 ' ' + loadTimeData
.getString('viewInactive') : '');
312 link
.textContent
= label
;
313 link
.addEventListener('click', function(e
) {
314 // TODO(estade): remove conversion to string?
315 chrome
.send('extensionSettingsInspect', [
316 String(extension
.id
),
317 String(view
.renderProcessId
),
318 String(view
.renderViewId
),
323 if (i
< extension
.views
.length
- 1) {
324 link
= link
.cloneNode(true);
325 activeViews
.appendChild(link
);
330 // The extension warnings (describing runtime issues).
331 if (extension
.warnings
) {
332 var panel
= node
.querySelector('.extension-warnings');
333 panel
.hidden
= false;
334 var list
= panel
.querySelector('ul');
335 extension
.warnings
.forEach(function(warning
) {
336 list
.appendChild(document
.createElement('li')).innerText
= warning
;
340 // If the ErrorConsole is enabled, we should have manifest and/or runtime
341 // errors. Otherwise, we may have install warnings. We should not have
342 // both ErrorConsole errors and install warnings.
343 if (extension
.manifestErrors
) {
344 var panel
= node
.querySelector('.manifest-errors');
345 panel
.hidden
= false;
346 panel
.appendChild(new extensions
.ExtensionErrorList(
347 extension
.manifestErrors
));
349 if (extension
.runtimeErrors
) {
350 var panel
= node
.querySelector('.runtime-errors');
351 panel
.hidden
= false;
352 panel
.appendChild(new extensions
.ExtensionErrorList(
353 extension
.runtimeErrors
));
355 if (extension
.installWarnings
) {
356 var panel
= node
.querySelector('.install-warnings');
357 panel
.hidden
= false;
358 var list
= panel
.querySelector('ul');
359 extension
.installWarnings
.forEach(function(warning
) {
360 var li
= document
.createElement('li');
361 li
.innerText
= warning
.message
;
362 list
.appendChild(li
);
366 this.appendChild(node
);
367 if (location
.hash
.substr(1) == extension
.id
) {
368 // Scroll beneath the fixed header so that the extension is not
370 var topScroll
= node
.offsetTop
- $('page-header').offsetHeight
;
371 var pad
= parseInt(getComputedStyle(node
, null).marginTop
, 10);
373 topScroll
-= pad
/ 2;
374 setScrollTopForDocument(document
, topScroll
);
380 ExtensionsList
: ExtensionsList