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',
141 this.handleToggleDevMode_.bind(this));
142 $('dev-controls').addEventListener('webkitTransitionEnd',
143 this.handleDevControlsTransitionEnd_.bind(this));
145 // Set up the three dev mode buttons (load unpacked, pack and update).
146 $('load-unpacked').addEventListener('click', function(e) {
147 extensionLoader.loadUnpacked();
149 $('pack-extension').addEventListener('click',
150 this.handlePackExtension_.bind(this));
151 $('update-extensions-now').addEventListener('click',
152 this.handleUpdateExtensionNow_.bind(this));
154 // Set up the close dialog for the apps developer tools promo.
155 $('apps-developer-tools-promo').querySelector('.close-button').
156 addEventListener('click', function(e) {
157 this.displayPromo_ = false;
158 this.updatePromoVisibility_();
159 chrome.send('extensionSettingsDismissADTPromo');
162 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
163 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
167 extensions.PackExtensionOverlay.getInstance().initializePage();
169 // Hook up the configure commands link to the overlay.
170 var link = document.querySelector('.extension-commands-config');
171 link.addEventListener('click',
172 this.handleExtensionCommandsConfig_.bind(this));
174 // Initialize the Commands overlay.
175 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
177 extensions.ExtensionErrorOverlay.getInstance().initializePage(
178 extensions.ExtensionSettings.showOverlay);
180 extensions.ExtensionOptionsOverlay.getInstance().initializePage(
181 extensions.ExtensionSettings.showOverlay);
183 // Initialize the kiosk overlay.
185 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
186 kioskOverlay.initialize();
188 $('add-kiosk-app').addEventListener('click', function() {
189 ExtensionSettings.showOverlay($('kiosk-apps-page'));
190 kioskOverlay.didShowPage();
193 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
196 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
197 cr.ui.overlay.globalInitialization();
199 extensions.ExtensionFocusManager.getInstance().initialize();
201 var path = document.location.pathname;
202 if (path.length > 1) {
203 // Skip starting slash and remove trailing slash (if any).
204 var overlayName = path.slice(1).replace(/\/$/, '');
205 if (overlayName == 'configureCommands')
206 this.showExtensionCommandsConfigUi_();
211 * Updates the Chrome Apps and Extensions Developer Tools promotion's
215 updatePromoVisibility_: function() {
216 var extensionSettings = $('extension-settings');
217 var visible = extensionSettings.classList.contains('dev-mode') &&
220 var adtPromo = $('apps-developer-tools-promo');
221 var controls = adtPromo.querySelectorAll('a, button');
222 Array.prototype.forEach.call(controls, function(control) {
223 control[visible ? 'removeAttribute' : 'setAttribute']('tabindex', '-1');
226 adtPromo.setAttribute('aria-hidden', !visible);
227 extensionSettings.classList.toggle('adt-promo', visible);
231 * Handles the Pack Extension button.
232 * @param {Event} e Change event.
235 handlePackExtension_: function(e) {
236 ExtensionSettings.showOverlay($('pack-extension-overlay'));
237 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
241 * Shows the Extension Commands configuration UI.
242 * @param {Event} e Change event.
245 showExtensionCommandsConfigUi_: function(e) {
246 ExtensionSettings.showOverlay($('extension-commands-overlay'));
247 chrome.send('metricsHandler:recordAction',
248 ['Options_ExtensionCommands']);
252 * Handles the Configure (Extension) Commands link.
253 * @param {Event} e Change event.
256 handleExtensionCommandsConfig_: function(e) {
257 this.showExtensionCommandsConfigUi_();
261 * Handles the Update Extension Now button.
262 * @param {Event} e Change event.
265 handleUpdateExtensionNow_: function(e) {
266 chrome.send('extensionSettingsAutoupdate');
270 * Handles the Toggle Dev Mode button.
271 * @param {Event} e Change event.
274 handleToggleDevMode_: function(e) {
275 if ($('toggle-dev-on').checked) {
276 $('dev-controls').hidden = false;
277 window.setTimeout(function() {
278 $('extension-settings').classList.add('dev-mode');
281 $('extension-settings').classList.remove('dev-mode');
283 window.setTimeout(this.updatePromoVisibility_.bind(this), 0);
285 chrome.send('extensionSettingsToggleDeveloperMode');
289 * Called when a transition has ended for #dev-controls.
290 * @param {Event} e webkitTransitionEnd event.
293 handleDevControlsTransitionEnd_: function(e) {
294 if (e.propertyName == 'height' &&
295 !$('extension-settings').classList.contains('dev-mode')) {
296 $('dev-controls').hidden = true;
302 * Called by the dom_ui_ to re-populate the page with data representing
303 * the current state of installed extensions.
304 * @param {ExtensionDataResponse} extensionsData
306 ExtensionSettings.returnExtensionsData = function(extensionsData) {
307 // We can get called many times in short order, thus we need to
308 // be careful to remove the 'finished loading' timeout.
309 if (this.loadingTimeout_)
310 window.clearTimeout(this.loadingTimeout_);
311 document.documentElement.classList.add('loading');
312 this.loadingTimeout_ = window.setTimeout(function() {
313 document.documentElement.classList.remove('loading');
316 webuiResponded = true;
318 if (extensionsData.extensions.length > 0) {
319 // Enforce order specified in the data or (if equal) then sort by
320 // extension name (case-insensitive) followed by their ID (in the case
321 // where extensions have the same name).
322 extensionsData.extensions.sort(function(a, b) {
323 function compare(x, y) {
324 return x < y ? -1 : (x > y ? 1 : 0);
326 return compare(a.order, b.order) ||
327 compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
332 var pageDiv = $('extension-settings');
334 if (extensionsData.profileIsSupervised) {
335 pageDiv.classList.add('profile-is-supervised');
337 pageDiv.classList.remove('profile-is-supervised');
339 if (extensionsData.profileIsSupervised) {
340 pageDiv.classList.add('showing-banner');
341 $('toggle-dev-on').disabled = true;
344 pageDiv.classList.remove('showing-banner');
345 $('toggle-dev-on').disabled = false;
348 pageDiv.style.marginTop = marginTop + 'px';
350 if (extensionsData.developerMode) {
351 pageDiv.classList.add('dev-mode');
352 $('toggle-dev-on').checked = true;
353 $('dev-controls').hidden = false;
355 pageDiv.classList.remove('dev-mode');
356 $('toggle-dev-on').checked = false;
359 ExtensionSettings.getInstance().displayPromo_ =
360 extensionsData.promoteAppsDevTools;
361 ExtensionSettings.getInstance().updatePromoVisibility_();
363 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
365 ExtensionsList.prototype.data_ = extensionsData;
366 var extensionList = $('extension-settings-list');
367 ExtensionsList.decorate(extensionList);
370 // Indicate that warning |message| has occured for pack of |crx_path| and
371 // |pem_path| files. Ask if user wants override the warning. Send
372 // |overrideFlags| to repeated 'pack' call to accomplish the override.
373 ExtensionSettings.askToOverrideWarning =
374 function(message, crx_path, pem_path, overrideFlags) {
375 var closeAlert = function() {
376 ExtensionSettings.showOverlay(null);
379 alertOverlay.setValues(
380 loadTimeData.getString('packExtensionWarningTitle'),
382 loadTimeData.getString('packExtensionProceedAnyway'),
383 loadTimeData.getString('cancel'),
385 chrome.send('pack', [crx_path, pem_path, overrideFlags]);
389 ExtensionSettings.showOverlay($('alertOverlay'));
393 * Returns the current overlay or null if one does not exist.
394 * @return {Element} The overlay element.
396 ExtensionSettings.getCurrentOverlay = function() {
397 return document.querySelector('#overlay .page.showing');
401 * Sets the given overlay to show. This hides whatever overlay is currently
403 * @param {HTMLElement} node The overlay page to show. If falsey, all overlays
406 ExtensionSettings.showOverlay = function(node) {
407 var pageDiv = $('extension-settings');
409 pageDiv.style.width = window.getComputedStyle(pageDiv).width;
410 document.body.classList.add('no-scroll');
412 document.body.classList.remove('no-scroll');
413 pageDiv.style.width = '';
416 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
417 if (currentlyShowingOverlay) {
418 currentlyShowingOverlay.classList.remove('showing');
419 cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
423 if (document.activeElement != document.body)
424 document.activeElement.blur();
425 node.classList.add('showing');
428 var pages = document.querySelectorAll('.page');
429 for (var i = 0; i < pages.length; i++) {
430 pages[i].setAttribute('aria-hidden', node ? 'true' : 'false');
433 $('overlay').hidden = !node;
434 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
435 'stopInterceptingEvents');
439 * Utility function to find the width of various UI strings and synchronize
440 * the width of relevant spans. This is crucial for making sure the
441 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
443 function measureCheckboxStrings() {
445 var measuringDiv = $('font-measuring-div');
446 measuringDiv.textContent =
447 loadTimeData.getString('extensionSettingsEnabled');
448 measuringDiv.className = 'enabled-text';
449 var pxWidth = measuringDiv.clientWidth + trashWidth;
450 measuringDiv.textContent =
451 loadTimeData.getString('extensionSettingsEnable');
452 measuringDiv.className = 'enable-text';
453 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
454 measuringDiv.textContent =
455 loadTimeData.getString('extensionSettingsDeveloperMode');
456 measuringDiv.className = '';
457 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
459 var style = document.createElement('style');
460 style.type = 'text/css';
462 '.enable-checkbox-text {' +
463 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
465 '#dev-toggle span {' +
466 ' min-width: ' + pxWidth + 'px;' +
468 document.querySelector('head').appendChild(style);
473 ExtensionSettings: ExtensionSettings
477 window.addEventListener('load', function(e) {
478 extensions.ExtensionSettings.getInstance().initialize();