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">
22 * The type of the extension data object. The definition is based on
23 * chrome/browser/ui/webui/extensions/extension_settings_handler.cc:
24 * ExtensionSettingsHandler::HandleRequestExtensionsData()
25 * @typedef {{developerMode: boolean,
26 * enableAppInfoDialog: boolean,
27 * incognitoAvailable: boolean,
28 * loadUnpackedDisabled: boolean,
29 * profileIsSupervised: boolean,
30 * promoteAppsDevTools: boolean}}
32 var ExtensionDataResponse
;
34 // Used for observing function of the backend datasource for this page by
36 var webuiResponded
= false;
38 cr
.define('extensions', function() {
39 var ExtensionList
= extensions
.ExtensionList
;
41 // Implements the DragWrapper handler interface.
42 var dragWrapperHandler
= {
44 shouldAcceptDrag: function(e
) {
45 // External Extension installation can be disabled globally, e.g. while a
46 // different overlay is already showing.
47 if (!ExtensionSettings
.getInstance().dragEnabled_
)
50 // We can't access filenames during the 'dragenter' event, so we have to
51 // wait until 'drop' to decide whether to do something with the file or
53 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
54 return (e
.dataTransfer
.types
&&
55 e
.dataTransfer
.types
.indexOf('Files') > -1);
58 doDragEnter: function() {
59 chrome
.send('startDrag');
60 ExtensionSettings
.showOverlay($('drop-target-overlay'));
63 doDragLeave: function() {
64 this.hideDropTargetOverlay_();
65 chrome
.send('stopDrag');
68 doDragOver: function(e
) {
73 this.hideDropTargetOverlay_();
74 if (e
.dataTransfer
.files
.length
!= 1)
78 // Files lack a check if they're a directory, but we can find out through
80 for (var i
= 0; i
< e
.dataTransfer
.items
.length
; ++i
) {
81 if (e
.dataTransfer
.items
[i
].kind
== 'file' &&
82 e
.dataTransfer
.items
[i
].webkitGetAsEntry().isDirectory
) {
83 toSend
= 'installDroppedDirectory';
87 // Only process files that look like extensions. Other files should
88 // navigate the browser normally.
90 /\.(crx|user\.js|zip)$/i.test(e
.dataTransfer
.files
[0].name
)) {
91 toSend
= 'installDroppedFile';
101 * Hide the current overlay if it is the drop target overlay.
104 hideDropTargetOverlay_: function() {
105 var currentOverlay
= ExtensionSettings
.getCurrentOverlay();
106 if (currentOverlay
&& currentOverlay
.id
=== 'drop-target-overlay')
107 ExtensionSettings
.showOverlay(null);
112 * ExtensionSettings class
115 function ExtensionSettings() {}
117 cr
.addSingletonGetter(ExtensionSettings
);
119 ExtensionSettings
.prototype = {
120 __proto__
: HTMLDivElement
.prototype,
123 * Whether or not to try to display the Apps Developer Tools promotion.
127 displayPromo_
: false,
130 * The drag-drop wrapper for installing external Extensions, if available.
131 * null if external Extension installation is not available.
132 * @type {cr.ui.DragWrapper}
138 * True if drag-drop is both available and currently enabled - it can be
139 * temporarily disabled while overlays are showing.
146 * Perform initial setup.
148 initialize: function() {
149 uber
.onContentFrameLoaded();
150 cr
.ui
.FocusOutlineManager
.forDocument(document
);
151 measureCheckboxStrings();
154 uber
.setTitle(loadTimeData
.getString('extensionSettings'));
156 var extensionList
= new ExtensionList();
157 extensionList
.id
= 'extension-settings-list';
158 var wrapper
= $('extension-list-wrapper');
159 wrapper
.insertBefore(extensionList
, wrapper
.firstChild
);
161 // This will request the data to show on the page and will get a response
162 // back in returnExtensionsData.
163 chrome
.send('extensionSettingsRequestExtensionsData');
165 var extensionLoader
= extensions
.ExtensionLoader
.getInstance();
167 $('toggle-dev-on').addEventListener('change', function(e
) {
168 this.updateDevControlsVisibility_(true);
169 extensionList
.updateFocusableElements();
170 chrome
.send('extensionSettingsToggleDeveloperMode',
171 [$('toggle-dev-on').checked
]);
174 window
.addEventListener('resize', function() {
175 this.updateDevControlsVisibility_(false);
178 // Set up the three dev mode buttons (load unpacked, pack and update).
179 $('load-unpacked').addEventListener('click', function(e
) {
180 extensionLoader
.loadUnpacked();
182 $('pack-extension').addEventListener('click',
183 this.handlePackExtension_
.bind(this));
184 $('update-extensions-now').addEventListener('click',
185 this.handleUpdateExtensionNow_
.bind(this));
187 // Set up the close dialog for the apps developer tools promo.
188 $('apps-developer-tools-promo').querySelector('.close-button').
189 addEventListener('click', function(e
) {
190 this.displayPromo_
= false;
191 this.updateDevControlsVisibility_(true);
192 chrome
.send('extensionSettingsDismissADTPromo');
194 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
195 $('update-extensions-now').focus();
198 if (!loadTimeData
.getBoolean('offStoreInstallEnabled')) {
199 this.dragWrapper_
= new cr
.ui
.DragWrapper(document
.documentElement
,
201 this.dragEnabled_
= true;
204 extensions
.PackExtensionOverlay
.getInstance().initializePage();
206 // Hook up the configure commands link to the overlay.
207 var link
= document
.querySelector('.extension-commands-config');
208 link
.addEventListener('click',
209 this.handleExtensionCommandsConfig_
.bind(this));
211 // Initialize the Commands overlay.
212 extensions
.ExtensionCommandsOverlay
.getInstance().initializePage();
214 extensions
.ExtensionErrorOverlay
.getInstance().initializePage(
215 extensions
.ExtensionSettings
.showOverlay
);
217 extensions
.ExtensionOptionsOverlay
.getInstance().initializePage(
218 extensions
.ExtensionSettings
.showOverlay
);
220 // Initialize the kiosk overlay.
222 var kioskOverlay
= extensions
.KioskAppsOverlay
.getInstance();
223 kioskOverlay
.initialize();
225 $('add-kiosk-app').addEventListener('click', function() {
226 ExtensionSettings
.showOverlay($('kiosk-apps-page'));
227 kioskOverlay
.didShowPage();
230 extensions
.KioskDisableBailoutConfirm
.getInstance().initialize();
233 cr
.ui
.overlay
.setupOverlay($('drop-target-overlay'));
234 cr
.ui
.overlay
.globalInitialization();
236 extensions
.ExtensionFocusManager
.getInstance().initialize();
238 var path
= document
.location
.pathname
;
239 if (path
.length
> 1) {
240 // Skip starting slash and remove trailing slash (if any).
241 var overlayName
= path
.slice(1).replace(/\/$/, '');
242 if (overlayName
== 'configureCommands')
243 this.showExtensionCommandsConfigUi_();
248 * Handles the Pack Extension button.
249 * @param {Event} e Change event.
252 handlePackExtension_: function(e
) {
253 ExtensionSettings
.showOverlay($('pack-extension-overlay'));
254 chrome
.send('metricsHandler:recordAction', ['Options_PackExtension']);
258 * Shows the Extension Commands configuration UI.
259 * @param {Event} e Change event.
262 showExtensionCommandsConfigUi_: function(e
) {
263 ExtensionSettings
.showOverlay($('extension-commands-overlay'));
264 chrome
.send('metricsHandler:recordAction',
265 ['Options_ExtensionCommands']);
269 * Handles the Configure (Extension) Commands link.
270 * @param {Event} e Change event.
273 handleExtensionCommandsConfig_: function(e
) {
274 this.showExtensionCommandsConfigUi_();
278 * Handles the Update Extension Now button.
279 * @param {Event} e Change event.
282 handleUpdateExtensionNow_: function(e
) {
283 chrome
.send('extensionSettingsAutoupdate');
287 * Updates the visibility of the developer controls based on whether the
288 * [x] Developer mode checkbox is checked. Also called if a user dismisses
289 * the apps developer tools promo.
290 * @param {boolean} animated Whether to animate any updates.
293 updateDevControlsVisibility_: function(animated
) {
294 var showDevControls
= $('toggle-dev-on').checked
;
295 $('extension-settings').classList
.toggle('dev-mode', showDevControls
);
297 var devControls
= $('dev-controls');
298 devControls
.classList
.toggle('animated', animated
);
300 var buttons
= devControls
.querySelector('.button-container');
301 var adtPromo
= $('apps-developer-tools-promo');
303 {root
: buttons
, focusable
: showDevControls
},
304 {root
: adtPromo
, focusable
: showDevControls
&& this.displayPromo_
},
305 ].forEach(function(entry
) {
306 var controls
= entry
.root
.querySelectorAll('a, button');
307 Array
.prototype.forEach
.call(controls
, function(control
) {
308 control
.tabIndex
= entry
.focusable
? 0 : -1;
310 entry
.root
.setAttribute('aria-hidden', !entry
.focusable
);
313 window
.requestAnimationFrame(function() {
314 devControls
.style
.height
= !showDevControls
? '' :
315 (this.displayPromo_
? adtPromo
.offsetHeight
: 0) +
316 buttons
.offsetHeight
+ 'px';
322 * Called by the dom_ui_ to re-populate the page with data representing
323 * the current state of installed extensions.
324 * @param {ExtensionDataResponse} extensionsData
326 ExtensionSettings
.returnExtensionsData = function(extensionsData
) {
327 webuiResponded
= true;
329 var supervised
= extensionsData
.profileIsSupervised
;
331 var pageDiv
= $('extension-settings');
332 pageDiv
.classList
.toggle('profile-is-supervised', supervised
);
333 pageDiv
.classList
.toggle('showing-banner', supervised
);
335 var devControlsCheckbox
= $('toggle-dev-on');
336 devControlsCheckbox
.checked
= extensionsData
.developerMode
;
337 devControlsCheckbox
.disabled
= supervised
;
339 var instance
= ExtensionSettings
.getInstance();
340 instance
.displayPromo_
= extensionsData
.promoteAppsDevTools
;
341 instance
.updateDevControlsVisibility_(false);
343 $('load-unpacked').disabled
= extensionsData
.loadUnpackedDisabled
;
344 var extensionList
= $('extension-settings-list');
345 extensionList
.updateExtensionsData(
346 extensionsData
.incognitoAvailable
,
347 extensionsData
.enableAppInfoDialog
).then(function() {
348 // We can get called many times in short order, thus we need to
349 // be careful to remove the 'finished loading' timeout.
350 if (this.loadingTimeout_
)
351 window
.clearTimeout(this.loadingTimeout_
);
352 document
.documentElement
.classList
.add('loading');
353 this.loadingTimeout_
= window
.setTimeout(function() {
354 document
.documentElement
.classList
.remove('loading');
357 var hasExtensions
= extensionList
.getNumExtensions() != 0;
358 $('no-extensions').hidden
= hasExtensions
;
359 $('extension-list-wrapper').hidden
= !hasExtensions
;
360 $('extension-settings-list').updateFocusableElements();
365 * Returns the current overlay or null if one does not exist.
366 * @return {Element} The overlay element.
368 ExtensionSettings
.getCurrentOverlay = function() {
369 return document
.querySelector('#overlay .page.showing');
373 * Sets the given overlay to show. If the overlay is already showing, this is
374 * a no-op; otherwise, hides any currently-showing overlay.
375 * @param {HTMLElement} node The overlay page to show. If null, all overlays
378 ExtensionSettings
.showOverlay = function(node
) {
379 var pageDiv
= $('extension-settings');
380 pageDiv
.style
.width
= node
? window
.getComputedStyle(pageDiv
).width
: '';
381 document
.body
.classList
.toggle('no-scroll', !!node
);
383 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
384 if (currentlyShowingOverlay
) {
385 if (currentlyShowingOverlay
== node
) // Already displayed.
387 currentlyShowingOverlay
.classList
.remove('showing');
388 cr
.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
392 var lastFocused
= document
.activeElement
;
393 $('overlay').addEventListener('cancelOverlay', function f() {
395 $('overlay').removeEventListener('cancelOverlay', f
);
397 node
.classList
.add('showing');
400 var pages
= document
.querySelectorAll('.page');
401 for (var i
= 0; i
< pages
.length
; i
++) {
402 pages
[i
].setAttribute('aria-hidden', node
? 'true' : 'false');
405 $('overlay').hidden
= !node
;
408 ExtensionSettings
.focusOverlay();
410 // If drag-drop for external Extension installation is available, enable
411 // drag-drop when there is any overlay showing other than the usual overlay
412 // shown when drag-drop is started.
413 var settings
= ExtensionSettings
.getInstance();
414 if (settings
.dragWrapper_
)
415 settings
.dragEnabled_
= !node
|| node
== $('drop-target-overlay');
417 uber
.invokeMethodOnParent(node
? 'beginInterceptingEvents' :
418 'stopInterceptingEvents');
421 ExtensionSettings
.focusOverlay = function() {
422 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
423 assert(currentlyShowingOverlay
);
425 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
426 cr
.ui
.setInitialFocus(currentlyShowingOverlay
);
428 if (!currentlyShowingOverlay
.contains(document
.activeElement
)) {
429 // Make sure focus isn't stuck behind the overlay.
430 document
.activeElement
.blur();
435 * Utility function to find the width of various UI strings and synchronize
436 * the width of relevant spans. This is crucial for making sure the
437 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
439 function measureCheckboxStrings() {
441 var measuringDiv
= $('font-measuring-div');
442 measuringDiv
.textContent
=
443 loadTimeData
.getString('extensionSettingsEnabled');
444 measuringDiv
.className
= 'enabled-text';
445 var pxWidth
= measuringDiv
.clientWidth
+ trashWidth
;
446 measuringDiv
.textContent
=
447 loadTimeData
.getString('extensionSettingsEnable');
448 measuringDiv
.className
= 'enable-text';
449 pxWidth
= Math
.max(measuringDiv
.clientWidth
+ trashWidth
, pxWidth
);
450 measuringDiv
.textContent
=
451 loadTimeData
.getString('extensionSettingsDeveloperMode');
452 measuringDiv
.className
= '';
453 pxWidth
= Math
.max(measuringDiv
.clientWidth
, pxWidth
);
455 var style
= document
.createElement('style');
456 style
.type
= 'text/css';
458 '.enable-checkbox-text {' +
459 ' min-width: ' + (pxWidth
- trashWidth
) + 'px;' +
461 '#dev-toggle span {' +
462 ' min-width: ' + pxWidth
+ 'px;' +
464 document
.querySelector('head').appendChild(style
);
469 ExtensionSettings
: ExtensionSettings
473 window
.addEventListener('load', function(e
) {
474 extensions
.ExtensionSettings
.getInstance().initialize();