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();