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
="focus_row.js">
13 <include src
="extension_list.js">
14 <include src
="pack_extension_overlay.js">
15 <include src
="extension_loader.js">
16 <include src
="extension_options_overlay.js">
19 <include src
="chromeos/kiosk_apps.js">
22 // Used for observing function of the backend datasource for this page by
24 var webuiResponded
= false;
26 cr
.define('extensions', function() {
27 var ExtensionList
= extensions
.ExtensionList
;
29 // Implements the DragWrapper handler interface.
30 var dragWrapperHandler
= {
32 shouldAcceptDrag: function(e
) {
33 // External Extension installation can be disabled globally, e.g. while a
34 // different overlay is already showing.
35 if (!ExtensionSettings
.getInstance().dragEnabled_
)
38 // We can't access filenames during the 'dragenter' event, so we have to
39 // wait until 'drop' to decide whether to do something with the file or
41 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
42 return (e
.dataTransfer
.types
&&
43 e
.dataTransfer
.types
.indexOf('Files') > -1);
46 doDragEnter: function() {
47 chrome
.send('startDrag');
48 ExtensionSettings
.showOverlay($('drop-target-overlay'));
51 doDragLeave: function() {
52 this.hideDropTargetOverlay_();
53 chrome
.send('stopDrag');
56 doDragOver: function(e
) {
61 this.hideDropTargetOverlay_();
62 if (e
.dataTransfer
.files
.length
!= 1)
66 // Files lack a check if they're a directory, but we can find out through
68 for (var i
= 0; i
< e
.dataTransfer
.items
.length
; ++i
) {
69 if (e
.dataTransfer
.items
[i
].kind
== 'file' &&
70 e
.dataTransfer
.items
[i
].webkitGetAsEntry().isDirectory
) {
71 toSend
= 'installDroppedDirectory';
75 // Only process files that look like extensions. Other files should
76 // navigate the browser normally.
78 /\.(crx|user\.js|zip)$/i.test(e
.dataTransfer
.files
[0].name
)) {
79 toSend
= 'installDroppedFile';
89 * Hide the current overlay if it is the drop target overlay.
92 hideDropTargetOverlay_: function() {
93 var currentOverlay
= ExtensionSettings
.getCurrentOverlay();
94 if (currentOverlay
&& currentOverlay
.id
=== 'drop-target-overlay')
95 ExtensionSettings
.showOverlay(null);
100 * ExtensionSettings class
103 * @implements {extensions.ExtensionListDelegate}
105 function ExtensionSettings() {}
107 cr
.addSingletonGetter(ExtensionSettings
);
109 ExtensionSettings
.prototype = {
110 __proto__
: HTMLDivElement
.prototype,
113 * The drag-drop wrapper for installing external Extensions, if available.
114 * null if external Extension installation is not available.
115 * @type {cr.ui.DragWrapper}
121 * True if drag-drop is both available and currently enabled - it can be
122 * temporarily disabled while overlays are showing.
129 * True if the page has finished the initial load.
135 * Perform initial setup.
137 initialize: function() {
138 this.setLoading_(true);
139 uber
.onContentFrameLoaded();
140 cr
.ui
.FocusOutlineManager
.forDocument(document
);
141 measureCheckboxStrings();
144 uber
.setTitle(loadTimeData
.getString('extensionSettings'));
146 var extensionList
= new ExtensionList(this);
147 extensionList
.id
= 'extension-settings-list';
148 var wrapper
= $('extension-list-wrapper');
149 wrapper
.insertBefore(extensionList
, wrapper
.firstChild
);
151 // Get the initial profile state, and register to be notified of any
153 chrome
.developerPrivate
.getProfileConfiguration(
154 this.update_
.bind(this));
155 chrome
.developerPrivate
.onProfileStateChanged
.addListener(
156 this.update_
.bind(this));
158 var extensionLoader
= extensions
.ExtensionLoader
.getInstance();
160 $('toggle-dev-on').addEventListener('change', function(e
) {
161 this.updateDevControlsVisibility_(true);
162 chrome
.developerPrivate
.updateProfileConfiguration(
163 {inDeveloperMode
: e
.target
.checked
});
164 var suffix
= $('toggle-dev-on').checked
? 'Enabled' : 'Disabled';
165 chrome
.send('metricsHandler:recordAction',
166 ['Options_ToggleDeveloperMode_' + suffix
]);
169 window
.addEventListener('resize', function() {
170 this.updateDevControlsVisibility_(false);
173 // Set up the three dev mode buttons (load unpacked, pack and update).
174 $('load-unpacked').addEventListener('click', function(e
) {
175 chrome
.send('metricsHandler:recordAction',
176 ['Options_LoadUnpackedExtension']);
177 extensionLoader
.loadUnpacked();
179 $('pack-extension').addEventListener('click',
180 this.handlePackExtension_
.bind(this));
181 $('update-extensions-now').addEventListener('click',
182 this.handleUpdateExtensionNow_
.bind(this));
184 if (!loadTimeData
.getBoolean('offStoreInstallEnabled')) {
185 this.dragWrapper_
= new cr
.ui
.DragWrapper(document
.documentElement
,
187 this.dragEnabled_
= true;
190 extensions
.PackExtensionOverlay
.getInstance().initializePage();
192 // Hook up the configure commands link to the overlay.
193 var link
= document
.querySelector('.extension-commands-config');
194 link
.addEventListener('click',
195 this.handleExtensionCommandsConfig_
.bind(this));
197 // Initialize the Commands overlay.
198 extensions
.ExtensionCommandsOverlay
.getInstance().initializePage();
200 extensions
.ExtensionErrorOverlay
.getInstance().initializePage(
201 extensions
.ExtensionSettings
.showOverlay
);
203 extensions
.ExtensionOptionsOverlay
.getInstance().initializePage(
204 extensions
.ExtensionSettings
.showOverlay
);
206 // Add user action logging for bottom links.
207 var moreExtensionLink
=
208 document
.getElementsByClassName('more-extensions-link');
209 for (var i
= 0; i
< moreExtensionLink
.length
; i
++) {
210 moreExtensionLink
[i
].addEventListener('click', function(e
) {
211 chrome
.send('metricsHandler:recordAction',
212 ['Options_GetMoreExtensions']);
216 // Initialize the kiosk overlay.
218 var kioskOverlay
= extensions
.KioskAppsOverlay
.getInstance();
219 kioskOverlay
.initialize();
221 $('add-kiosk-app').addEventListener('click', function() {
222 ExtensionSettings
.showOverlay($('kiosk-apps-page'));
223 kioskOverlay
.didShowPage();
226 extensions
.KioskDisableBailoutConfirm
.getInstance().initialize();
229 cr
.ui
.overlay
.setupOverlay($('drop-target-overlay'));
230 cr
.ui
.overlay
.globalInitialization();
232 extensions
.ExtensionFocusManager
.getInstance().initialize();
234 var path
= document
.location
.pathname
;
235 if (path
.length
> 1) {
236 // Skip starting slash and remove trailing slash (if any).
237 var overlayName
= path
.slice(1).replace(/\/$/, '');
238 if (overlayName
== 'configureCommands')
239 this.showExtensionCommandsConfigUi_();
244 * [Re]-Populates the page with data representing the current state of
245 * installed extensions.
246 * @param {ProfileInfo} profileInfo
249 update_: function(profileInfo
) {
250 // We only set the page to be loading if we haven't already finished an
251 // initial load, because otherwise the updates are all incremental and
252 // don't need to display the interstitial spinner.
253 if (!this.hasLoaded_
)
254 this.setLoading_(true);
255 webuiResponded
= true;
258 var supervised
= profileInfo
.isSupervised
;
260 var pageDiv
= $('extension-settings');
261 pageDiv
.classList
.toggle('profile-is-supervised', supervised
);
262 pageDiv
.classList
.toggle('showing-banner', supervised
);
264 var devControlsCheckbox
= $('toggle-dev-on');
265 devControlsCheckbox
.checked
= profileInfo
.inDeveloperMode
;
266 devControlsCheckbox
.disabled
= supervised
;
268 $('load-unpacked').disabled
= !profileInfo
.canLoadUnpacked
;
269 var extensionList
= $('extension-settings-list');
270 extensionList
.updateExtensionsData(
271 profileInfo
.isIncognitoAvailable
,
272 profileInfo
.appInfoDialogEnabled
).then(function() {
273 if (!this.hasLoaded_
) {
274 this.hasLoaded_
= true;
275 this.setLoading_(false);
277 this.onExtensionCountChanged();
282 * Shows the loading spinner and hides elements that shouldn't be visible
284 * @param {boolean} isLoading
287 setLoading_: function(isLoading
) {
288 document
.documentElement
.classList
.toggle('loading', isLoading
);
289 $('loading-spinner').hidden
= !isLoading
;
290 $('dev-controls').hidden
= isLoading
;
291 this.updateDevControlsVisibility_(false);
293 // The extension list is already hidden/shown elsewhere and shouldn't be
294 // updated here because it can be hidden if there are no extensions.
298 * Handles the Pack Extension button.
299 * @param {Event} e Change event.
302 handlePackExtension_: function(e
) {
303 ExtensionSettings
.showOverlay($('pack-extension-overlay'));
304 chrome
.send('metricsHandler:recordAction', ['Options_PackExtension']);
308 * Shows the Extension Commands configuration UI.
311 showExtensionCommandsConfigUi_: function() {
312 ExtensionSettings
.showOverlay($('extension-commands-overlay'));
313 chrome
.send('metricsHandler:recordAction',
314 ['Options_ExtensionCommands']);
318 * Handles the Configure (Extension) Commands link.
319 * @param {Event} e Change event.
322 handleExtensionCommandsConfig_: function(e
) {
323 this.showExtensionCommandsConfigUi_();
327 * Handles the Update Extension Now button.
328 * @param {Event} e Change event.
331 handleUpdateExtensionNow_: function(e
) {
332 chrome
.developerPrivate
.autoUpdate();
333 chrome
.send('metricsHandler:recordAction',
334 ['Options_UpdateExtensions']);
338 * Updates the visibility of the developer controls based on whether the
339 * [x] Developer mode checkbox is checked.
340 * @param {boolean} animated Whether to animate any updates.
343 updateDevControlsVisibility_: function(animated
) {
344 var showDevControls
= $('toggle-dev-on').checked
;
345 $('extension-settings').classList
.toggle('dev-mode', showDevControls
);
347 var devControls
= $('dev-controls');
348 devControls
.classList
.toggle('animated', animated
);
350 var buttons
= devControls
.querySelector('.button-container');
351 Array
.prototype.forEach
.call(buttons
.querySelectorAll('a, button'),
353 control
.tabIndex
= showDevControls
? 0 : -1;
355 buttons
.setAttribute('aria-hidden', !showDevControls
);
357 window
.requestAnimationFrame(function() {
358 devControls
.style
.height
= !showDevControls
? '' :
359 buttons
.offsetHeight
+ 'px';
361 document
.dispatchEvent(new Event('devControlsVisibilityUpdated'));
366 onExtensionCountChanged: function() {
368 var hasExtensions
= $('extension-settings-list').getNumExtensions() != 0;
369 $('no-extensions').hidden
= hasExtensions
;
370 $('extension-list-wrapper').hidden
= !hasExtensions
;
375 * Returns the current overlay or null if one does not exist.
376 * @return {Element} The overlay element.
378 ExtensionSettings
.getCurrentOverlay = function() {
379 return document
.querySelector('#overlay .page.showing');
383 * Sets the given overlay to show. If the overlay is already showing, this is
384 * a no-op; otherwise, hides any currently-showing overlay.
385 * @param {HTMLElement} node The overlay page to show. If null, all overlays
388 ExtensionSettings
.showOverlay = function(node
) {
389 var pageDiv
= $('extension-settings');
390 pageDiv
.style
.width
= node
? window
.getComputedStyle(pageDiv
).width
: '';
391 document
.body
.classList
.toggle('no-scroll', !!node
);
393 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
394 if (currentlyShowingOverlay
) {
395 if (currentlyShowingOverlay
== node
) // Already displayed.
397 currentlyShowingOverlay
.classList
.remove('showing');
403 var focusOutlineManager
= cr
.ui
.FocusOutlineManager
.forDocument(document
);
404 if (focusOutlineManager
.visible
)
405 lastFocused
= document
.activeElement
;
407 $('overlay').addEventListener('cancelOverlay', function f() {
408 if (lastFocused
&& focusOutlineManager
.visible
)
411 $('overlay').removeEventListener('cancelOverlay', f
);
412 uber
.replaceState({}, '');
414 node
.classList
.add('showing');
417 var pages
= document
.querySelectorAll('.page');
418 for (var i
= 0; i
< pages
.length
; i
++) {
419 pages
[i
].setAttribute('aria-hidden', node
? 'true' : 'false');
422 $('overlay').hidden
= !node
;
425 ExtensionSettings
.focusOverlay();
427 // If drag-drop for external Extension installation is available, enable
428 // drag-drop when there is any overlay showing other than the usual overlay
429 // shown when drag-drop is started.
430 var settings
= ExtensionSettings
.getInstance();
431 if (settings
.dragWrapper_
)
432 settings
.dragEnabled_
= !node
|| node
== $('drop-target-overlay');
434 uber
.invokeMethodOnParent(node
? 'beginInterceptingEvents' :
435 'stopInterceptingEvents');
438 ExtensionSettings
.focusOverlay = function() {
439 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
440 assert(currentlyShowingOverlay
);
442 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
443 cr
.ui
.setInitialFocus(currentlyShowingOverlay
);
445 if (!currentlyShowingOverlay
.contains(document
.activeElement
)) {
446 // Make sure focus isn't stuck behind the overlay.
447 document
.activeElement
.blur();
452 * Utility function to find the width of various UI strings and synchronize
453 * the width of relevant spans. This is crucial for making sure the
454 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
456 function measureCheckboxStrings() {
458 var measuringDiv
= $('font-measuring-div');
459 measuringDiv
.textContent
=
460 loadTimeData
.getString('extensionSettingsEnabled');
461 measuringDiv
.className
= 'enabled-text';
462 var pxWidth
= measuringDiv
.clientWidth
+ trashWidth
;
463 measuringDiv
.textContent
=
464 loadTimeData
.getString('extensionSettingsEnable');
465 measuringDiv
.className
= 'enable-text';
466 pxWidth
= Math
.max(measuringDiv
.clientWidth
+ trashWidth
, pxWidth
);
467 measuringDiv
.textContent
=
468 loadTimeData
.getString('extensionSettingsDeveloperMode');
469 measuringDiv
.className
= '';
470 pxWidth
= Math
.max(measuringDiv
.clientWidth
, pxWidth
);
472 var style
= document
.createElement('style');
473 style
.type
= 'text/css';
475 '.enable-checkbox-text {' +
476 ' min-width: ' + (pxWidth
- trashWidth
) + 'px;' +
478 '#dev-toggle span {' +
479 ' min-width: ' + pxWidth
+ 'px;' +
481 document
.querySelector('head').appendChild(style
);
486 ExtensionSettings
: ExtensionSettings
490 window
.addEventListener('load', function(e
) {
491 extensions
.ExtensionSettings
.getInstance().initialize();