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
="../uber/uber_utils.js"></include
>
6 <include src
="extension_code.js"></include
>
7 <include src
="extension_commands_overlay.js"></include
>
8 <include src
="extension_focus_manager.js"></include
>
9 <include src
="extension_list.js"></include
>
10 <include src
="pack_extension_overlay.js"></include
>
11 <include src
="extension_error_overlay.js"></include
>
12 <include src
="extension_loader.js"></include
>
15 <include src
="chromeos/kiosk_apps.js"></include
>
18 // Used for observing function of the backend datasource for this page by
20 var webuiResponded
= false;
22 cr
.define('extensions', function() {
23 var ExtensionsList
= options
.ExtensionsList
;
25 // Implements the DragWrapper handler interface.
26 var dragWrapperHandler
= {
28 shouldAcceptDrag: function(e
) {
29 // We can't access filenames during the 'dragenter' event, so we have to
30 // wait until 'drop' to decide whether to do something with the file or
32 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
33 return (e
.dataTransfer
.types
&&
34 e
.dataTransfer
.types
.indexOf('Files') > -1);
37 doDragEnter: function() {
38 chrome
.send('startDrag');
39 ExtensionSettings
.showOverlay(null);
40 ExtensionSettings
.showOverlay($('drop-target-overlay'));
43 doDragLeave: function() {
44 ExtensionSettings
.showOverlay(null);
45 chrome
.send('stopDrag');
48 doDragOver: function(e
) {
53 ExtensionSettings
.showOverlay(null);
54 if (e
.dataTransfer
.files
.length
!= 1)
58 // Files lack a check if they're a directory, but we can find out through
60 for (var i
= 0; i
< e
.dataTransfer
.items
.length
; ++i
) {
61 if (e
.dataTransfer
.items
[i
].kind
== 'file' &&
62 e
.dataTransfer
.items
[i
].webkitGetAsEntry().isDirectory
) {
63 toSend
= 'installDroppedDirectory';
67 // Only process files that look like extensions. Other files should
68 // navigate the browser normally.
69 if (!toSend
&& /\.(crx|user\.js)$/i.test(e
.dataTransfer
.files
[0].name
))
70 toSend
= 'installDroppedFile';
80 * ExtensionSettings class
83 function ExtensionSettings() {}
85 cr
.addSingletonGetter(ExtensionSettings
);
87 ExtensionSettings
.prototype = {
88 __proto__
: HTMLDivElement
.prototype,
91 * Whether or not to try to display the Apps Developer Tools promotion.
98 * Perform initial setup.
100 initialize: function() {
101 uber
.onContentFrameLoaded();
102 cr
.ui
.FocusOutlineManager
.forDocument(document
);
103 measureCheckboxStrings();
106 var title
= loadTimeData
.getString('extensionSettings');
107 uber
.invokeMethodOnParent('setTitle', {title
: title
});
109 // This will request the data to show on the page and will get a response
110 // back in returnExtensionsData.
111 chrome
.send('extensionSettingsRequestExtensionsData');
113 var extensionLoader
= extensions
.ExtensionLoader
.getInstance();
115 $('toggle-dev-on').addEventListener('change',
116 this.handleToggleDevMode_
.bind(this));
117 $('dev-controls').addEventListener('webkitTransitionEnd',
118 this.handleDevControlsTransitionEnd_
.bind(this));
120 // Set up the three dev mode buttons (load unpacked, pack and update).
121 $('load-unpacked').addEventListener('click', function(e
) {
122 extensionLoader
.loadUnpacked();
124 $('pack-extension').addEventListener('click',
125 this.handlePackExtension_
.bind(this));
126 $('update-extensions-now').addEventListener('click',
127 this.handleUpdateExtensionNow_
.bind(this));
129 // Set up the close dialog for the apps developer tools promo.
130 $('apps-developer-tools-promo').querySelector('.close-button').
131 addEventListener('click', function(e
) {
132 this.displayPromo_
= false;
133 this.updatePromoVisibility_();
134 chrome
.send('extensionSettingsDismissADTPromo');
137 if (!loadTimeData
.getBoolean('offStoreInstallEnabled')) {
138 this.dragWrapper_
= new cr
.ui
.DragWrapper(document
.documentElement
,
142 extensions
.PackExtensionOverlay
.getInstance().initializePage();
144 // Hook up the configure commands link to the overlay.
145 var link
= document
.querySelector('.extension-commands-config');
146 link
.addEventListener('click',
147 this.handleExtensionCommandsConfig_
.bind(this));
149 // Initialize the Commands overlay.
150 extensions
.ExtensionCommandsOverlay
.getInstance().initializePage();
152 extensions
.ExtensionErrorOverlay
.getInstance().initializePage(
153 extensions
.ExtensionSettings
.showOverlay
);
155 // Initialize the kiosk overlay.
157 var kioskOverlay
= extensions
.KioskAppsOverlay
.getInstance();
158 kioskOverlay
.initialize();
160 $('add-kiosk-app').addEventListener('click', function() {
161 ExtensionSettings
.showOverlay($('kiosk-apps-page'));
162 kioskOverlay
.didShowPage();
165 extensions
.KioskDisableBailoutConfirm
.getInstance().initialize();
168 cr
.ui
.overlay
.setupOverlay($('drop-target-overlay'));
169 cr
.ui
.overlay
.globalInitialization();
171 extensions
.ExtensionFocusManager
.getInstance().initialize();
173 var path
= document
.location
.pathname
;
174 if (path
.length
> 1) {
175 // Skip starting slash and remove trailing slash (if any).
176 var overlayName
= path
.slice(1).replace(/\/$/, '');
177 if (overlayName
== 'configureCommands')
178 this.showExtensionCommandsConfigUi_();
181 preventDefaultOnPoundLinkClicks(); // From webui/js/util.js.
185 * Updates the Chrome Apps and Extensions Developer Tools promotion's
189 updatePromoVisibility_: function() {
190 var extensionSettings
= $('extension-settings');
191 var visible
= extensionSettings
.classList
.contains('dev-mode') &&
194 var adtPromo
= $('apps-developer-tools-promo');
195 var controls
= adtPromo
.querySelectorAll('a, button');
196 Array
.prototype.forEach
.call(controls
, function(control
) {
197 control
[visible
? 'removeAttribute' : 'setAttribute']('tabindex', '-1');
200 adtPromo
.setAttribute('aria-hidden', !visible
);
201 extensionSettings
.classList
.toggle('adt-promo', visible
);
205 * Handles the Pack Extension button.
206 * @param {Event} e Change event.
209 handlePackExtension_: function(e
) {
210 ExtensionSettings
.showOverlay($('pack-extension-overlay'));
211 chrome
.send('metricsHandler:recordAction', ['Options_PackExtension']);
215 * Shows the Extension Commands configuration UI.
216 * @param {Event} e Change event.
219 showExtensionCommandsConfigUi_: function(e
) {
220 ExtensionSettings
.showOverlay($('extension-commands-overlay'));
221 chrome
.send('metricsHandler:recordAction',
222 ['Options_ExtensionCommands']);
226 * Handles the Configure (Extension) Commands link.
227 * @param {Event} e Change event.
230 handleExtensionCommandsConfig_: function(e
) {
231 this.showExtensionCommandsConfigUi_();
235 * Handles the Update Extension Now button.
236 * @param {Event} e Change event.
239 handleUpdateExtensionNow_: function(e
) {
240 chrome
.send('extensionSettingsAutoupdate');
244 * Handles the Toggle Dev Mode button.
245 * @param {Event} e Change event.
248 handleToggleDevMode_: function(e
) {
249 if ($('toggle-dev-on').checked
) {
250 $('dev-controls').hidden
= false;
251 window
.setTimeout(function() {
252 $('extension-settings').classList
.add('dev-mode');
255 $('extension-settings').classList
.remove('dev-mode');
257 window
.setTimeout(this.updatePromoVisibility_
.bind(this));
259 chrome
.send('extensionSettingsToggleDeveloperMode');
263 * Called when a transition has ended for #dev-controls.
264 * @param {Event} e webkitTransitionEnd event.
267 handleDevControlsTransitionEnd_: function(e
) {
268 if (e
.propertyName
== 'height' &&
269 !$('extension-settings').classList
.contains('dev-mode')) {
270 $('dev-controls').hidden
= true;
276 * Called by the dom_ui_ to re-populate the page with data representing
277 * the current state of installed extensions.
279 ExtensionSettings
.returnExtensionsData = function(extensionsData
) {
280 // We can get called many times in short order, thus we need to
281 // be careful to remove the 'finished loading' timeout.
282 if (this.loadingTimeout_
)
283 window
.clearTimeout(this.loadingTimeout_
);
284 document
.documentElement
.classList
.add('loading');
285 this.loadingTimeout_
= window
.setTimeout(function() {
286 document
.documentElement
.classList
.remove('loading');
289 webuiResponded
= true;
291 if (extensionsData
.extensions
.length
> 0) {
292 // Enforce order specified in the data or (if equal) then sort by
293 // extension name (case-insensitive) followed by their ID (in the case
294 // where extensions have the same name).
295 extensionsData
.extensions
.sort(function(a
, b
) {
296 function compare(x
, y
) {
297 return x
< y
? -1 : (x
> y
? 1 : 0);
299 return compare(a
.order
, b
.order
) ||
300 compare(a
.name
.toLowerCase(), b
.name
.toLowerCase()) ||
305 var pageDiv
= $('extension-settings');
307 if (extensionsData
.profileIsManaged
) {
308 pageDiv
.classList
.add('profile-is-managed');
310 pageDiv
.classList
.remove('profile-is-managed');
312 if (extensionsData
.profileIsManaged
) {
313 pageDiv
.classList
.add('showing-banner');
314 $('toggle-dev-on').disabled
= true;
317 pageDiv
.classList
.remove('showing-banner');
318 $('toggle-dev-on').disabled
= false;
321 pageDiv
.style
.marginTop
= marginTop
+ 'px';
323 if (extensionsData
.developerMode
) {
324 pageDiv
.classList
.add('dev-mode');
325 $('toggle-dev-on').checked
= true;
326 $('dev-controls').hidden
= false;
328 pageDiv
.classList
.remove('dev-mode');
329 $('toggle-dev-on').checked
= false;
332 ExtensionSettings
.getInstance().displayPromo_
=
333 extensionsData
.promoteAppsDevTools
;
334 ExtensionSettings
.getInstance().updatePromoVisibility_();
336 $('load-unpacked').disabled
= extensionsData
.loadUnpackedDisabled
;
338 ExtensionsList
.prototype.data_
= extensionsData
;
339 var extensionList
= $('extension-settings-list');
340 ExtensionsList
.decorate(extensionList
);
343 // Indicate that warning |message| has occured for pack of |crx_path| and
344 // |pem_path| files. Ask if user wants override the warning. Send
345 // |overrideFlags| to repeated 'pack' call to accomplish the override.
346 ExtensionSettings
.askToOverrideWarning
=
347 function(message
, crx_path
, pem_path
, overrideFlags
) {
348 var closeAlert = function() {
349 ExtensionSettings
.showOverlay(null);
352 alertOverlay
.setValues(
353 loadTimeData
.getString('packExtensionWarningTitle'),
355 loadTimeData
.getString('packExtensionProceedAnyway'),
356 loadTimeData
.getString('cancel'),
358 chrome
.send('pack', [crx_path
, pem_path
, overrideFlags
]);
362 ExtensionSettings
.showOverlay($('alertOverlay'));
366 * Returns the current overlay or null if one does not exist.
367 * @return {Element} The overlay element.
369 ExtensionSettings
.getCurrentOverlay = function() {
370 return document
.querySelector('#overlay .page.showing');
374 * Sets the given overlay to show. This hides whatever overlay is currently
376 * @param {HTMLElement} node The overlay page to show. If falsey, all overlays
379 ExtensionSettings
.showOverlay = function(node
) {
380 var pageDiv
= $('extension-settings');
382 pageDiv
.style
.width
= window
.getComputedStyle(pageDiv
).width
;
383 document
.body
.classList
.add('no-scroll');
385 document
.body
.classList
.remove('no-scroll');
386 pageDiv
.style
.width
= '';
389 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
390 if (currentlyShowingOverlay
)
391 currentlyShowingOverlay
.classList
.remove('showing');
394 node
.classList
.add('showing');
396 var pages
= document
.querySelectorAll('.page');
397 for (var i
= 0; i
< pages
.length
; i
++) {
398 pages
[i
].setAttribute('aria-hidden', node
? 'true' : 'false');
401 overlay
.hidden
= !node
;
402 uber
.invokeMethodOnParent(node
? 'beginInterceptingEvents' :
403 'stopInterceptingEvents');
407 * Utility function to find the width of various UI strings and synchronize
408 * the width of relevant spans. This is crucial for making sure the
409 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
411 function measureCheckboxStrings() {
413 var measuringDiv
= $('font-measuring-div');
414 measuringDiv
.textContent
=
415 loadTimeData
.getString('extensionSettingsEnabled');
416 var pxWidth
= measuringDiv
.clientWidth
+ trashWidth
;
417 measuringDiv
.textContent
=
418 loadTimeData
.getString('extensionSettingsEnable');
419 pxWidth
= Math
.max(measuringDiv
.clientWidth
+ trashWidth
, pxWidth
);
420 measuringDiv
.textContent
=
421 loadTimeData
.getString('extensionSettingsDeveloperMode');
422 pxWidth
= Math
.max(measuringDiv
.clientWidth
, pxWidth
);
424 var style
= document
.createElement('style');
425 style
.type
= 'text/css';
427 '.enable-checkbox-text {' +
428 ' min-width: ' + (pxWidth
- trashWidth
) + 'px;' +
430 '#dev-toggle span {' +
431 ' min-width: ' + pxWidth
+ 'px;' +
433 document
.querySelector('head').appendChild(style
);
438 ExtensionSettings
: ExtensionSettings
442 window
.addEventListener('load', function(e
) {
443 extensions
.ExtensionSettings
.getInstance().initialize();