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
="../../../../ui/webui/resources/js/cr/ui/focus_row.js">
6 <include src
="../../../../ui/webui/resources/js/cr/ui/focus_grid.js">
7 <include src
="../uber/uber_utils.js">
8 <include src
="extension_code.js">
9 <include src
="extension_commands_overlay.js">
10 <include src
="extension_error_overlay.js">
11 <include src
="extension_focus_manager.js">
12 <include src
="extension_list.js">
13 <include src
="pack_extension_overlay.js">
14 <include src
="extension_loader.js">
15 <include src
="extension_options_overlay.js">
18 <include src
="chromeos/kiosk_apps.js">
21 // Used for observing function of the backend datasource for this page by
23 var webuiResponded
= false;
25 cr
.define('extensions', function() {
26 var ExtensionList
= extensions
.ExtensionList
;
28 // Implements the DragWrapper handler interface.
29 var dragWrapperHandler
= {
31 shouldAcceptDrag: function(e
) {
32 // External Extension installation can be disabled globally, e.g. while a
33 // different overlay is already showing.
34 if (!ExtensionSettings
.getInstance().dragEnabled_
)
37 // We can't access filenames during the 'dragenter' event, so we have to
38 // wait until 'drop' to decide whether to do something with the file or
40 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
41 return (e
.dataTransfer
.types
&&
42 e
.dataTransfer
.types
.indexOf('Files') > -1);
45 doDragEnter: function() {
46 chrome
.send('startDrag');
47 ExtensionSettings
.showOverlay($('drop-target-overlay'));
50 doDragLeave: function() {
51 this.hideDropTargetOverlay_();
52 chrome
.send('stopDrag');
55 doDragOver: function(e
) {
60 this.hideDropTargetOverlay_();
61 if (e
.dataTransfer
.files
.length
!= 1)
65 // Files lack a check if they're a directory, but we can find out through
67 for (var i
= 0; i
< e
.dataTransfer
.items
.length
; ++i
) {
68 if (e
.dataTransfer
.items
[i
].kind
== 'file' &&
69 e
.dataTransfer
.items
[i
].webkitGetAsEntry().isDirectory
) {
70 toSend
= 'installDroppedDirectory';
74 // Only process files that look like extensions. Other files should
75 // navigate the browser normally.
77 /\.(crx|user\.js|zip)$/i.test(e
.dataTransfer
.files
[0].name
)) {
78 toSend
= 'installDroppedFile';
88 * Hide the current overlay if it is the drop target overlay.
91 hideDropTargetOverlay_: function() {
92 var currentOverlay
= ExtensionSettings
.getCurrentOverlay();
93 if (currentOverlay
&& currentOverlay
.id
=== 'drop-target-overlay')
94 ExtensionSettings
.showOverlay(null);
99 * ExtensionSettings class
102 * @implements {extensions.ExtensionListDelegate}
104 function ExtensionSettings() {}
106 cr
.addSingletonGetter(ExtensionSettings
);
108 ExtensionSettings
.prototype = {
109 __proto__
: HTMLDivElement
.prototype,
112 * The drag-drop wrapper for installing external Extensions, if available.
113 * null if external Extension installation is not available.
114 * @type {cr.ui.DragWrapper}
120 * True if drag-drop is both available and currently enabled - it can be
121 * temporarily disabled while overlays are showing.
128 * Perform initial setup.
130 initialize: function() {
131 this.setLoading_(true);
132 uber
.onContentFrameLoaded();
133 cr
.ui
.FocusOutlineManager
.forDocument(document
);
134 measureCheckboxStrings();
137 uber
.setTitle(loadTimeData
.getString('extensionSettings'));
139 var extensionList
= new ExtensionList(this);
140 extensionList
.id
= 'extension-settings-list';
141 var wrapper
= $('extension-list-wrapper');
142 wrapper
.insertBefore(extensionList
, wrapper
.firstChild
);
144 // Get the initial profile state, and register to be notified of any
146 chrome
.developerPrivate
.getProfileConfiguration(
147 this.update_
.bind(this));
148 chrome
.developerPrivate
.onProfileStateChanged
.addListener(
149 this.update_
.bind(this));
151 var extensionLoader
= extensions
.ExtensionLoader
.getInstance();
153 $('toggle-dev-on').addEventListener('change', function(e
) {
154 this.updateDevControlsVisibility_(true);
155 extensionList
.updateFocusableElements();
156 chrome
.developerPrivate
.updateProfileConfiguration(
157 {inDeveloperMode
: e
.target
.checked
});
158 var suffix
= $('toggle-dev-on').checked
? 'Enabled' : 'Disabled';
159 chrome
.send('metricsHandler:recordAction',
160 ['Options_ToggleDeveloperMode_' + suffix
]);
163 window
.addEventListener('resize', function() {
164 this.updateDevControlsVisibility_(false);
167 // Set up the three dev mode buttons (load unpacked, pack and update).
168 $('load-unpacked').addEventListener('click', function(e
) {
169 chrome
.send('metricsHandler:recordAction',
170 ['Options_LoadUnpackedExtension']);
171 extensionLoader
.loadUnpacked();
173 $('pack-extension').addEventListener('click',
174 this.handlePackExtension_
.bind(this));
175 $('update-extensions-now').addEventListener('click',
176 this.handleUpdateExtensionNow_
.bind(this));
178 if (!loadTimeData
.getBoolean('offStoreInstallEnabled')) {
179 this.dragWrapper_
= new cr
.ui
.DragWrapper(document
.documentElement
,
181 this.dragEnabled_
= true;
184 extensions
.PackExtensionOverlay
.getInstance().initializePage();
186 // Hook up the configure commands link to the overlay.
187 var link
= document
.querySelector('.extension-commands-config');
188 link
.addEventListener('click',
189 this.handleExtensionCommandsConfig_
.bind(this));
191 // Initialize the Commands overlay.
192 extensions
.ExtensionCommandsOverlay
.getInstance().initializePage();
194 extensions
.ExtensionErrorOverlay
.getInstance().initializePage(
195 extensions
.ExtensionSettings
.showOverlay
);
197 extensions
.ExtensionOptionsOverlay
.getInstance().initializePage(
198 extensions
.ExtensionSettings
.showOverlay
);
200 // Add user action logging for bottom links.
201 var moreExtensionLink
=
202 document
.getElementsByClassName('more-extensions-link');
203 for (var i
= 0; i
< moreExtensionLink
.length
; i
++) {
204 moreExtensionLink
[i
].addEventListener('click', function(e
) {
205 chrome
.send('metricsHandler:recordAction',
206 ['Options_GetMoreExtensions']);
210 // Initialize the kiosk overlay.
212 var kioskOverlay
= extensions
.KioskAppsOverlay
.getInstance();
213 kioskOverlay
.initialize();
215 $('add-kiosk-app').addEventListener('click', function() {
216 ExtensionSettings
.showOverlay($('kiosk-apps-page'));
217 kioskOverlay
.didShowPage();
220 extensions
.KioskDisableBailoutConfirm
.getInstance().initialize();
223 cr
.ui
.overlay
.setupOverlay($('drop-target-overlay'));
224 cr
.ui
.overlay
.globalInitialization();
226 extensions
.ExtensionFocusManager
.getInstance().initialize();
228 var path
= document
.location
.pathname
;
229 if (path
.length
> 1) {
230 // Skip starting slash and remove trailing slash (if any).
231 var overlayName
= path
.slice(1).replace(/\/$/, '');
232 if (overlayName
== 'configureCommands')
233 this.showExtensionCommandsConfigUi_();
238 * [Re]-Populates the page with data representing the current state of
239 * installed extensions.
240 * @param {ProfileInfo} profileInfo
243 update_: function(profileInfo
) {
244 this.setLoading_(true);
245 webuiResponded
= true;
248 var supervised
= profileInfo
.isSupervised
;
250 var pageDiv
= $('extension-settings');
251 pageDiv
.classList
.toggle('profile-is-supervised', supervised
);
252 pageDiv
.classList
.toggle('showing-banner', supervised
);
254 var devControlsCheckbox
= $('toggle-dev-on');
255 devControlsCheckbox
.checked
= profileInfo
.inDeveloperMode
;
256 devControlsCheckbox
.disabled
= supervised
;
258 $('load-unpacked').disabled
= !profileInfo
.canLoadUnpacked
;
259 var extensionList
= $('extension-settings-list');
260 extensionList
.updateExtensionsData(
261 profileInfo
.isIncognitoAvailable
,
262 profileInfo
.appInfoDialogEnabled
).then(function() {
263 this.setLoading_(false);
264 this.onExtensionCountChanged();
269 * Shows the loading spinner and hides elements that shouldn't be visible
271 * @param {boolean} isLoading
274 setLoading_: function(isLoading
) {
275 document
.documentElement
.classList
.toggle('loading', isLoading
);
276 $('loading-spinner').hidden
= !isLoading
;
277 $('dev-controls').hidden
= isLoading
;
278 this.updateDevControlsVisibility_(false);
280 // The extension list is already hidden/shown elsewhere and shouldn't be
281 // updated here because it can be hidden if there are no extensions.
285 * Handles the Pack Extension button.
286 * @param {Event} e Change event.
289 handlePackExtension_: function(e
) {
290 ExtensionSettings
.showOverlay($('pack-extension-overlay'));
291 chrome
.send('metricsHandler:recordAction', ['Options_PackExtension']);
295 * Shows the Extension Commands configuration UI.
298 showExtensionCommandsConfigUi_: function() {
299 ExtensionSettings
.showOverlay($('extension-commands-overlay'));
300 chrome
.send('metricsHandler:recordAction',
301 ['Options_ExtensionCommands']);
305 * Handles the Configure (Extension) Commands link.
306 * @param {Event} e Change event.
309 handleExtensionCommandsConfig_: function(e
) {
310 this.showExtensionCommandsConfigUi_();
314 * Handles the Update Extension Now button.
315 * @param {Event} e Change event.
318 handleUpdateExtensionNow_: function(e
) {
319 chrome
.developerPrivate
.autoUpdate();
320 chrome
.send('metricsHandler:recordAction',
321 ['Options_UpdateExtensions']);
325 * Updates the visibility of the developer controls based on whether the
326 * [x] Developer mode checkbox is checked.
327 * @param {boolean} animated Whether to animate any updates.
330 updateDevControlsVisibility_: function(animated
) {
331 var showDevControls
= $('toggle-dev-on').checked
;
332 $('extension-settings').classList
.toggle('dev-mode', showDevControls
);
334 var devControls
= $('dev-controls');
335 devControls
.classList
.toggle('animated', animated
);
337 var buttons
= devControls
.querySelector('.button-container');
338 Array
.prototype.forEach
.call(buttons
.querySelectorAll('a, button'),
340 control
.tabIndex
= showDevControls
? 0 : -1;
342 buttons
.setAttribute('aria-hidden', !showDevControls
);
344 window
.requestAnimationFrame(function() {
345 devControls
.style
.height
= !showDevControls
? '' :
346 buttons
.offsetHeight
+ 'px';
348 document
.dispatchEvent(new Event('devControlsVisibilityUpdated'));
353 onExtensionCountChanged: function() {
355 var hasExtensions
= $('extension-settings-list').getNumExtensions() != 0;
356 $('no-extensions').hidden
= hasExtensions
;
357 $('extension-list-wrapper').hidden
= !hasExtensions
;
362 * Returns the current overlay or null if one does not exist.
363 * @return {Element} The overlay element.
365 ExtensionSettings
.getCurrentOverlay = function() {
366 return document
.querySelector('#overlay .page.showing');
370 * Sets the given overlay to show. If the overlay is already showing, this is
371 * a no-op; otherwise, hides any currently-showing overlay.
372 * @param {HTMLElement} node The overlay page to show. If null, all overlays
375 ExtensionSettings
.showOverlay = function(node
) {
376 var pageDiv
= $('extension-settings');
377 pageDiv
.style
.width
= node
? window
.getComputedStyle(pageDiv
).width
: '';
378 document
.body
.classList
.toggle('no-scroll', !!node
);
380 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
381 if (currentlyShowingOverlay
) {
382 if (currentlyShowingOverlay
== node
) // Already displayed.
384 currentlyShowingOverlay
.classList
.remove('showing');
390 var focusOutlineManager
= cr
.ui
.FocusOutlineManager
.forDocument(document
);
391 if (focusOutlineManager
.visible
)
392 lastFocused
= document
.activeElement
;
394 $('overlay').addEventListener('cancelOverlay', function f() {
395 console
.log('cancelOverlay');
396 console
.log('lastFocused', lastFocused
);
397 console
.log('focusOutlineManager.visible', focusOutlineManager
.visible
);
398 if (lastFocused
&& focusOutlineManager
.visible
)
401 $('overlay').removeEventListener('cancelOverlay', f
);
403 node
.classList
.add('showing');
406 var pages
= document
.querySelectorAll('.page');
407 for (var i
= 0; i
< pages
.length
; i
++) {
408 pages
[i
].setAttribute('aria-hidden', node
? 'true' : 'false');
411 $('overlay').hidden
= !node
;
414 ExtensionSettings
.focusOverlay();
416 // If drag-drop for external Extension installation is available, enable
417 // drag-drop when there is any overlay showing other than the usual overlay
418 // shown when drag-drop is started.
419 var settings
= ExtensionSettings
.getInstance();
420 if (settings
.dragWrapper_
)
421 settings
.dragEnabled_
= !node
|| node
== $('drop-target-overlay');
423 uber
.invokeMethodOnParent(node
? 'beginInterceptingEvents' :
424 'stopInterceptingEvents');
427 ExtensionSettings
.focusOverlay = function() {
428 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
429 assert(currentlyShowingOverlay
);
431 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
432 cr
.ui
.setInitialFocus(currentlyShowingOverlay
);
434 if (!currentlyShowingOverlay
.contains(document
.activeElement
)) {
435 // Make sure focus isn't stuck behind the overlay.
436 document
.activeElement
.blur();
441 * Utility function to find the width of various UI strings and synchronize
442 * the width of relevant spans. This is crucial for making sure the
443 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
445 function measureCheckboxStrings() {
447 var measuringDiv
= $('font-measuring-div');
448 measuringDiv
.textContent
=
449 loadTimeData
.getString('extensionSettingsEnabled');
450 measuringDiv
.className
= 'enabled-text';
451 var pxWidth
= measuringDiv
.clientWidth
+ trashWidth
;
452 measuringDiv
.textContent
=
453 loadTimeData
.getString('extensionSettingsEnable');
454 measuringDiv
.className
= 'enable-text';
455 pxWidth
= Math
.max(measuringDiv
.clientWidth
+ trashWidth
, pxWidth
);
456 measuringDiv
.textContent
=
457 loadTimeData
.getString('extensionSettingsDeveloperMode');
458 measuringDiv
.className
= '';
459 pxWidth
= Math
.max(measuringDiv
.clientWidth
, pxWidth
);
461 var style
= document
.createElement('style');
462 style
.type
= 'text/css';
464 '.enable-checkbox-text {' +
465 ' min-width: ' + (pxWidth
- trashWidth
) + 'px;' +
467 '#dev-toggle span {' +
468 ' min-width: ' + pxWidth
+ 'px;' +
470 document
.querySelector('head').appendChild(style
);
475 ExtensionSettings
: ExtensionSettings
479 window
.addEventListener('load', function(e
) {
480 extensions
.ExtensionSettings
.getInstance().initialize();