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">
6 <include src
="extension_code.js">
7 <include src
="extension_commands_overlay.js">
8 <include src
="extension_error_overlay.js">
9 <include src
="extension_focus_manager.js">
10 <include src
="extension_list.js">
11 <include src
="pack_extension_overlay.js">
12 <include src
="extension_loader.js">
13 <include src
="extension_options_overlay.js">
16 <include src
="chromeos/kiosk_apps.js">
20 * The type of the extension data object. The definition is based on
21 * chrome/browser/ui/webui/extensions/extension_settings_handler.cc:
22 * ExtensionSettingsHandler::HandleRequestExtensionsData()
23 * @typedef {{developerMode: boolean,
25 * incognitoAvailable: boolean,
26 * loadUnpackedDisabled: boolean,
27 * profileIsSupervised: boolean,
28 * promoteAppsDevTools: boolean}}
30 var ExtensionDataResponse
;
32 // Used for observing function of the backend datasource for this page by
34 var webuiResponded
= false;
36 cr
.define('extensions', function() {
37 var ExtensionsList
= options
.ExtensionsList
;
39 // Implements the DragWrapper handler interface.
40 var dragWrapperHandler
= {
42 shouldAcceptDrag: function(e
) {
43 // We can't access filenames during the 'dragenter' event, so we have to
44 // wait until 'drop' to decide whether to do something with the file or
46 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
47 return (e
.dataTransfer
.types
&&
48 e
.dataTransfer
.types
.indexOf('Files') > -1);
51 doDragEnter: function() {
52 chrome
.send('startDrag');
53 ExtensionSettings
.showOverlay(null);
54 ExtensionSettings
.showOverlay($('drop-target-overlay'));
57 doDragLeave: function() {
58 this.hideDropTargetOverlay_();
59 chrome
.send('stopDrag');
62 doDragOver: function(e
) {
67 this.hideDropTargetOverlay_();
68 if (e
.dataTransfer
.files
.length
!= 1)
72 // Files lack a check if they're a directory, but we can find out through
74 for (var i
= 0; i
< e
.dataTransfer
.items
.length
; ++i
) {
75 if (e
.dataTransfer
.items
[i
].kind
== 'file' &&
76 e
.dataTransfer
.items
[i
].webkitGetAsEntry().isDirectory
) {
77 toSend
= 'installDroppedDirectory';
81 // Only process files that look like extensions. Other files should
82 // navigate the browser normally.
84 /\.(crx|user\.js|zip)$/i.test(e
.dataTransfer
.files
[0].name
)) {
85 toSend
= 'installDroppedFile';
95 * Hide the current overlay if it is the drop target overlay.
98 hideDropTargetOverlay_: function() {
99 var currentOverlay
= ExtensionSettings
.getCurrentOverlay();
100 if (currentOverlay
&& currentOverlay
.id
=== 'drop-target-overlay')
101 ExtensionSettings
.showOverlay(null);
106 * ExtensionSettings class
109 function ExtensionSettings() {}
111 cr
.addSingletonGetter(ExtensionSettings
);
113 ExtensionSettings
.prototype = {
114 __proto__
: HTMLDivElement
.prototype,
117 * Whether or not to try to display the Apps Developer Tools promotion.
121 displayPromo_
: false,
124 * Perform initial setup.
126 initialize: function() {
127 uber
.onContentFrameLoaded();
128 cr
.ui
.FocusOutlineManager
.forDocument(document
);
129 measureCheckboxStrings();
132 uber
.setTitle(loadTimeData
.getString('extensionSettings'));
134 // This will request the data to show on the page and will get a response
135 // back in returnExtensionsData.
136 chrome
.send('extensionSettingsRequestExtensionsData');
138 var extensionLoader
= extensions
.ExtensionLoader
.getInstance();
140 $('toggle-dev-on').addEventListener('change', function(e
) {
141 this.updateDevControlsVisibility_(true);
142 chrome
.send('extensionSettingsToggleDeveloperMode',
143 [$('toggle-dev-on').checked
]);
146 window
.addEventListener('resize', function() {
147 this.updateDevControlsVisibility_(false);
150 // Set up the three dev mode buttons (load unpacked, pack and update).
151 $('load-unpacked').addEventListener('click', function(e
) {
152 extensionLoader
.loadUnpacked();
154 $('pack-extension').addEventListener('click',
155 this.handlePackExtension_
.bind(this));
156 $('update-extensions-now').addEventListener('click',
157 this.handleUpdateExtensionNow_
.bind(this));
159 // Set up the close dialog for the apps developer tools promo.
160 $('apps-developer-tools-promo').querySelector('.close-button').
161 addEventListener('click', function(e
) {
162 this.displayPromo_
= false;
163 this.updateDevControlsVisibility_(true);
164 chrome
.send('extensionSettingsDismissADTPromo');
166 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
167 $('update-extensions-now').focus();
170 if (!loadTimeData
.getBoolean('offStoreInstallEnabled')) {
171 this.dragWrapper_
= new cr
.ui
.DragWrapper(document
.documentElement
,
175 extensions
.PackExtensionOverlay
.getInstance().initializePage();
177 // Hook up the configure commands link to the overlay.
178 var link
= document
.querySelector('.extension-commands-config');
179 link
.addEventListener('click',
180 this.handleExtensionCommandsConfig_
.bind(this));
182 // Initialize the Commands overlay.
183 extensions
.ExtensionCommandsOverlay
.getInstance().initializePage();
185 extensions
.ExtensionErrorOverlay
.getInstance().initializePage(
186 extensions
.ExtensionSettings
.showOverlay
);
188 extensions
.ExtensionOptionsOverlay
.getInstance().initializePage(
189 extensions
.ExtensionSettings
.showOverlay
);
191 // Initialize the kiosk overlay.
193 var kioskOverlay
= extensions
.KioskAppsOverlay
.getInstance();
194 kioskOverlay
.initialize();
196 $('add-kiosk-app').addEventListener('click', function() {
197 ExtensionSettings
.showOverlay($('kiosk-apps-page'));
198 kioskOverlay
.didShowPage();
201 extensions
.KioskDisableBailoutConfirm
.getInstance().initialize();
204 cr
.ui
.overlay
.setupOverlay($('drop-target-overlay'));
205 cr
.ui
.overlay
.globalInitialization();
207 extensions
.ExtensionFocusManager
.getInstance().initialize();
209 var path
= document
.location
.pathname
;
210 if (path
.length
> 1) {
211 // Skip starting slash and remove trailing slash (if any).
212 var overlayName
= path
.slice(1).replace(/\/$/, '');
213 if (overlayName
== 'configureCommands')
214 this.showExtensionCommandsConfigUi_();
219 * Handles the Pack Extension button.
220 * @param {Event} e Change event.
223 handlePackExtension_: function(e
) {
224 ExtensionSettings
.showOverlay($('pack-extension-overlay'));
225 chrome
.send('metricsHandler:recordAction', ['Options_PackExtension']);
229 * Shows the Extension Commands configuration UI.
230 * @param {Event} e Change event.
233 showExtensionCommandsConfigUi_: function(e
) {
234 ExtensionSettings
.showOverlay($('extension-commands-overlay'));
235 chrome
.send('metricsHandler:recordAction',
236 ['Options_ExtensionCommands']);
240 * Handles the Configure (Extension) Commands link.
241 * @param {Event} e Change event.
244 handleExtensionCommandsConfig_: function(e
) {
245 this.showExtensionCommandsConfigUi_();
249 * Handles the Update Extension Now button.
250 * @param {Event} e Change event.
253 handleUpdateExtensionNow_: function(e
) {
254 chrome
.send('extensionSettingsAutoupdate');
258 * Updates the visibility of the developer controls based on whether the
259 * [x] Developer mode checkbox is checked. Also called if a user dismisses
260 * the apps developer tools promo.
261 * @param {boolean} animated Whether to animate any updates.
264 updateDevControlsVisibility_: function(animated
) {
265 var showDevControls
= $('toggle-dev-on').checked
;
266 $('extension-settings').classList
.toggle('dev-mode', showDevControls
);
268 var devControls
= $('dev-controls');
269 devControls
.classList
.toggle('animated', animated
);
271 var buttons
= devControls
.querySelector('.button-container');
272 var adtPromo
= $('apps-developer-tools-promo');
274 {root
: buttons
, focusable
: showDevControls
},
275 {root
: adtPromo
, focusable
: showDevControls
&& this.displayPromo_
},
276 ].forEach(function(entry
) {
277 var controls
= entry
.root
.querySelectorAll('a, button');
278 Array
.prototype.forEach
.call(controls
, function(control
) {
279 control
.tabIndex
= entry
.focusable
? 0 : -1;
281 entry
.root
.setAttribute('aria-hidden', !entry
.focusable
);
284 window
.requestAnimationFrame(function() {
285 devControls
.style
.height
= !showDevControls
? '' :
286 (this.displayPromo_
? adtPromo
.offsetHeight
: 0) +
287 buttons
.offsetHeight
+ 'px';
293 * Called by the dom_ui_ to re-populate the page with data representing
294 * the current state of installed extensions.
295 * @param {ExtensionDataResponse} extensionsData
297 ExtensionSettings
.returnExtensionsData = function(extensionsData
) {
298 // We can get called many times in short order, thus we need to
299 // be careful to remove the 'finished loading' timeout.
300 if (this.loadingTimeout_
)
301 window
.clearTimeout(this.loadingTimeout_
);
302 document
.documentElement
.classList
.add('loading');
303 this.loadingTimeout_
= window
.setTimeout(function() {
304 document
.documentElement
.classList
.remove('loading');
307 webuiResponded
= true;
309 if (extensionsData
.extensions
.length
> 0) {
310 // Enforce order specified in the data or (if equal) then sort by
311 // extension name (case-insensitive) followed by their ID (in the case
312 // where extensions have the same name).
313 extensionsData
.extensions
.sort(function(a
, b
) {
314 function compare(x
, y
) {
315 return x
< y
? -1 : (x
> y
? 1 : 0);
317 return compare(a
.order
, b
.order
) ||
318 compare(a
.name
.toLowerCase(), b
.name
.toLowerCase()) ||
323 var supervised
= extensionsData
.profileIsSupervised
;
325 var pageDiv
= $('extension-settings');
326 pageDiv
.classList
.toggle('profile-is-supervised', supervised
);
327 pageDiv
.classList
.toggle('showing-banner', supervised
);
329 var devControlsCheckbox
= $('toggle-dev-on');
330 devControlsCheckbox
.checked
= extensionsData
.developerMode
;
331 devControlsCheckbox
.disabled
= supervised
;
333 var instance
= ExtensionSettings
.getInstance();
334 instance
.displayPromo_
= extensionsData
.promoteAppsDevTools
;
335 instance
.updateDevControlsVisibility_(false);
337 $('load-unpacked').disabled
= extensionsData
.loadUnpackedDisabled
;
339 ExtensionsList
.prototype.data_
= extensionsData
;
340 var extensionList
= $('extension-settings-list');
341 ExtensionsList
.decorate(extensionList
);
344 // Indicate that warning |message| has occured for pack of |crx_path| and
345 // |pem_path| files. Ask if user wants override the warning. Send
346 // |overrideFlags| to repeated 'pack' call to accomplish the override.
347 ExtensionSettings
.askToOverrideWarning
=
348 function(message
, crx_path
, pem_path
, overrideFlags
) {
349 var closeAlert = function() {
350 ExtensionSettings
.showOverlay(null);
353 alertOverlay
.setValues(
354 loadTimeData
.getString('packExtensionWarningTitle'),
356 loadTimeData
.getString('packExtensionProceedAnyway'),
357 loadTimeData
.getString('cancel'),
359 chrome
.send('pack', [crx_path
, pem_path
, overrideFlags
]);
363 ExtensionSettings
.showOverlay($('alertOverlay'));
367 * Returns the current overlay or null if one does not exist.
368 * @return {Element} The overlay element.
370 ExtensionSettings
.getCurrentOverlay = function() {
371 return document
.querySelector('#overlay .page.showing');
375 * Sets the given overlay to show. This hides whatever overlay is currently
377 * @param {HTMLElement} node The overlay page to show. If null, all overlays
380 ExtensionSettings
.showOverlay = function(node
) {
381 var pageDiv
= $('extension-settings');
382 pageDiv
.style
.width
= node
? window
.getComputedStyle(pageDiv
).width
: '';
383 document
.body
.classList
.toggle('no-scroll', !!node
);
385 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
386 if (currentlyShowingOverlay
) {
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 uber
.invokeMethodOnParent(node
? 'beginInterceptingEvents' :
411 'stopInterceptingEvents');
414 ExtensionSettings
.focusOverlay = function() {
415 var currentlyShowingOverlay
= ExtensionSettings
.getCurrentOverlay();
416 assert(currentlyShowingOverlay
);
418 if (cr
.ui
.FocusOutlineManager
.forDocument(document
).visible
)
419 cr
.ui
.setInitialFocus(currentlyShowingOverlay
);
421 if (!currentlyShowingOverlay
.contains(document
.activeElement
)) {
422 // Make sure focus isn't stuck behind the overlay.
423 document
.activeElement
.blur();
428 * Utility function to find the width of various UI strings and synchronize
429 * the width of relevant spans. This is crucial for making sure the
430 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
432 function measureCheckboxStrings() {
434 var measuringDiv
= $('font-measuring-div');
435 measuringDiv
.textContent
=
436 loadTimeData
.getString('extensionSettingsEnabled');
437 measuringDiv
.className
= 'enabled-text';
438 var pxWidth
= measuringDiv
.clientWidth
+ trashWidth
;
439 measuringDiv
.textContent
=
440 loadTimeData
.getString('extensionSettingsEnable');
441 measuringDiv
.className
= 'enable-text';
442 pxWidth
= Math
.max(measuringDiv
.clientWidth
+ trashWidth
, pxWidth
);
443 measuringDiv
.textContent
=
444 loadTimeData
.getString('extensionSettingsDeveloperMode');
445 measuringDiv
.className
= '';
446 pxWidth
= Math
.max(measuringDiv
.clientWidth
, pxWidth
);
448 var style
= document
.createElement('style');
449 style
.type
= 'text/css';
451 '.enable-checkbox-text {' +
452 ' min-width: ' + (pxWidth
- trashWidth
) + 'px;' +
454 '#dev-toggle span {' +
455 ' min-width: ' + pxWidth
+ 'px;' +
457 document
.querySelector('head').appendChild(style
);
462 ExtensionSettings
: ExtensionSettings
466 window
.addEventListener('load', function(e
) {
467 extensions
.ExtensionSettings
.getInstance().initialize();