ozone: evdev: Sync caps lock LED state to evdev
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extensions.js
blob6500f07cbf32e1035b53ec1bb74840eeeb161c3a
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">
17 <if expr="chromeos">
18 <include src="chromeos/kiosk_apps.js">
19 </if>
21 /**
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 * extensions: Array,
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
35 // tests.
36 var webuiResponded = false;
38 cr.define('extensions', function() {
39 var ExtensionList = extensions.ExtensionList;
41 // Implements the DragWrapper handler interface.
42 var dragWrapperHandler = {
43 /** @override */
44 shouldAcceptDrag: function(e) {
45 // We can't access filenames during the 'dragenter' event, so we have to
46 // wait until 'drop' to decide whether to do something with the file or
47 // not.
48 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
49 return (e.dataTransfer.types &&
50 e.dataTransfer.types.indexOf('Files') > -1);
52 /** @override */
53 doDragEnter: function() {
54 chrome.send('startDrag');
55 ExtensionSettings.showOverlay(null);
56 ExtensionSettings.showOverlay($('drop-target-overlay'));
58 /** @override */
59 doDragLeave: function() {
60 this.hideDropTargetOverlay_();
61 chrome.send('stopDrag');
63 /** @override */
64 doDragOver: function(e) {
65 e.preventDefault();
67 /** @override */
68 doDrop: function(e) {
69 this.hideDropTargetOverlay_();
70 if (e.dataTransfer.files.length != 1)
71 return;
73 var toSend = null;
74 // Files lack a check if they're a directory, but we can find out through
75 // its item entry.
76 for (var i = 0; i < e.dataTransfer.items.length; ++i) {
77 if (e.dataTransfer.items[i].kind == 'file' &&
78 e.dataTransfer.items[i].webkitGetAsEntry().isDirectory) {
79 toSend = 'installDroppedDirectory';
80 break;
83 // Only process files that look like extensions. Other files should
84 // navigate the browser normally.
85 if (!toSend &&
86 /\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) {
87 toSend = 'installDroppedFile';
90 if (toSend) {
91 e.preventDefault();
92 chrome.send(toSend);
96 /**
97 * Hide the current overlay if it is the drop target overlay.
98 * @private
100 hideDropTargetOverlay_: function() {
101 var currentOverlay = ExtensionSettings.getCurrentOverlay();
102 if (currentOverlay && currentOverlay.id === 'drop-target-overlay')
103 ExtensionSettings.showOverlay(null);
108 * ExtensionSettings class
109 * @class
111 function ExtensionSettings() {}
113 cr.addSingletonGetter(ExtensionSettings);
115 ExtensionSettings.prototype = {
116 __proto__: HTMLDivElement.prototype,
119 * Whether or not to try to display the Apps Developer Tools promotion.
120 * @type {boolean}
121 * @private
123 displayPromo_: false,
126 * Perform initial setup.
128 initialize: function() {
129 uber.onContentFrameLoaded();
130 cr.ui.FocusOutlineManager.forDocument(document);
131 measureCheckboxStrings();
133 // Set the title.
134 uber.setTitle(loadTimeData.getString('extensionSettings'));
136 // This will request the data to show on the page and will get a response
137 // back in returnExtensionsData.
138 chrome.send('extensionSettingsRequestExtensionsData');
140 var extensionLoader = extensions.ExtensionLoader.getInstance();
142 $('toggle-dev-on').addEventListener('change', function(e) {
143 this.updateDevControlsVisibility_(true);
144 $('extension-settings-list').updateFocusableElements();
145 chrome.send('extensionSettingsToggleDeveloperMode',
146 [$('toggle-dev-on').checked]);
147 }.bind(this));
149 window.addEventListener('resize', function() {
150 this.updateDevControlsVisibility_(false);
151 }.bind(this));
153 // Set up the three dev mode buttons (load unpacked, pack and update).
154 $('load-unpacked').addEventListener('click', function(e) {
155 extensionLoader.loadUnpacked();
157 $('pack-extension').addEventListener('click',
158 this.handlePackExtension_.bind(this));
159 $('update-extensions-now').addEventListener('click',
160 this.handleUpdateExtensionNow_.bind(this));
162 // Set up the close dialog for the apps developer tools promo.
163 $('apps-developer-tools-promo').querySelector('.close-button').
164 addEventListener('click', function(e) {
165 this.displayPromo_ = false;
166 this.updateDevControlsVisibility_(true);
167 chrome.send('extensionSettingsDismissADTPromo');
169 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
170 $('update-extensions-now').focus();
171 }.bind(this));
173 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
174 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
175 dragWrapperHandler);
178 extensions.PackExtensionOverlay.getInstance().initializePage();
180 // Hook up the configure commands link to the overlay.
181 var link = document.querySelector('.extension-commands-config');
182 link.addEventListener('click',
183 this.handleExtensionCommandsConfig_.bind(this));
185 // Initialize the Commands overlay.
186 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
188 extensions.ExtensionErrorOverlay.getInstance().initializePage(
189 extensions.ExtensionSettings.showOverlay);
191 extensions.ExtensionOptionsOverlay.getInstance().initializePage(
192 extensions.ExtensionSettings.showOverlay);
194 // Initialize the kiosk overlay.
195 if (cr.isChromeOS) {
196 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
197 kioskOverlay.initialize();
199 $('add-kiosk-app').addEventListener('click', function() {
200 ExtensionSettings.showOverlay($('kiosk-apps-page'));
201 kioskOverlay.didShowPage();
204 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
207 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
208 cr.ui.overlay.globalInitialization();
210 extensions.ExtensionFocusManager.getInstance().initialize();
212 var path = document.location.pathname;
213 if (path.length > 1) {
214 // Skip starting slash and remove trailing slash (if any).
215 var overlayName = path.slice(1).replace(/\/$/, '');
216 if (overlayName == 'configureCommands')
217 this.showExtensionCommandsConfigUi_();
222 * Handles the Pack Extension button.
223 * @param {Event} e Change event.
224 * @private
226 handlePackExtension_: function(e) {
227 ExtensionSettings.showOverlay($('pack-extension-overlay'));
228 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
232 * Shows the Extension Commands configuration UI.
233 * @param {Event} e Change event.
234 * @private
236 showExtensionCommandsConfigUi_: function(e) {
237 ExtensionSettings.showOverlay($('extension-commands-overlay'));
238 chrome.send('metricsHandler:recordAction',
239 ['Options_ExtensionCommands']);
243 * Handles the Configure (Extension) Commands link.
244 * @param {Event} e Change event.
245 * @private
247 handleExtensionCommandsConfig_: function(e) {
248 this.showExtensionCommandsConfigUi_();
252 * Handles the Update Extension Now button.
253 * @param {Event} e Change event.
254 * @private
256 handleUpdateExtensionNow_: function(e) {
257 chrome.send('extensionSettingsAutoupdate');
261 * Updates the visibility of the developer controls based on whether the
262 * [x] Developer mode checkbox is checked. Also called if a user dismisses
263 * the apps developer tools promo.
264 * @param {boolean} animated Whether to animate any updates.
265 * @private
267 updateDevControlsVisibility_: function(animated) {
268 var showDevControls = $('toggle-dev-on').checked;
269 $('extension-settings').classList.toggle('dev-mode', showDevControls);
271 var devControls = $('dev-controls');
272 devControls.classList.toggle('animated', animated);
274 var buttons = devControls.querySelector('.button-container');
275 var adtPromo = $('apps-developer-tools-promo');
277 {root: buttons, focusable: showDevControls},
278 {root: adtPromo, focusable: showDevControls && this.displayPromo_},
279 ].forEach(function(entry) {
280 var controls = entry.root.querySelectorAll('a, button');
281 Array.prototype.forEach.call(controls, function(control) {
282 control.tabIndex = entry.focusable ? 0 : -1;
284 entry.root.setAttribute('aria-hidden', !entry.focusable);
287 window.requestAnimationFrame(function() {
288 devControls.style.height = !showDevControls ? '' :
289 (this.displayPromo_ ? adtPromo.offsetHeight : 0) +
290 buttons.offsetHeight + 'px';
291 }.bind(this));
296 * Called by the dom_ui_ to re-populate the page with data representing
297 * the current state of installed extensions.
298 * @param {ExtensionDataResponse} extensionsData
300 ExtensionSettings.returnExtensionsData = function(extensionsData) {
301 // We can get called many times in short order, thus we need to
302 // be careful to remove the 'finished loading' timeout.
303 if (this.loadingTimeout_)
304 window.clearTimeout(this.loadingTimeout_);
305 document.documentElement.classList.add('loading');
306 this.loadingTimeout_ = window.setTimeout(function() {
307 document.documentElement.classList.remove('loading');
308 }, 0);
310 webuiResponded = true;
312 if (extensionsData.extensions.length > 0) {
313 // Enforce order specified in the data or (if equal) then sort by
314 // extension name (case-insensitive) followed by their ID (in the case
315 // where extensions have the same name).
316 extensionsData.extensions.sort(function(a, b) {
317 function compare(x, y) {
318 return x < y ? -1 : (x > y ? 1 : 0);
320 return compare(a.order, b.order) ||
321 compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
322 compare(a.id, b.id);
326 var supervised = extensionsData.profileIsSupervised;
328 var pageDiv = $('extension-settings');
329 pageDiv.classList.toggle('profile-is-supervised', supervised);
330 pageDiv.classList.toggle('showing-banner', supervised);
332 var devControlsCheckbox = $('toggle-dev-on');
333 devControlsCheckbox.checked = extensionsData.developerMode;
334 devControlsCheckbox.disabled = supervised;
336 var instance = ExtensionSettings.getInstance();
337 instance.displayPromo_ = extensionsData.promoteAppsDevTools;
338 instance.updateDevControlsVisibility_(false);
340 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
342 ExtensionList.prototype.data_ = extensionsData;
343 ExtensionList.decorate($('extension-settings-list'));
347 * Returns the current overlay or null if one does not exist.
348 * @return {Element} The overlay element.
350 ExtensionSettings.getCurrentOverlay = function() {
351 return document.querySelector('#overlay .page.showing');
355 * Sets the given overlay to show. This hides whatever overlay is currently
356 * showing, if any.
357 * @param {HTMLElement} node The overlay page to show. If null, all overlays
358 * are hidden.
360 ExtensionSettings.showOverlay = function(node) {
361 var pageDiv = $('extension-settings');
362 pageDiv.style.width = node ? window.getComputedStyle(pageDiv).width : '';
363 document.body.classList.toggle('no-scroll', !!node);
365 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
366 if (currentlyShowingOverlay) {
367 currentlyShowingOverlay.classList.remove('showing');
368 cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
371 if (node) {
372 var lastFocused = document.activeElement;
373 $('overlay').addEventListener('cancelOverlay', function f() {
374 lastFocused.focus();
375 $('overlay').removeEventListener('cancelOverlay', f);
377 node.classList.add('showing');
380 var pages = document.querySelectorAll('.page');
381 for (var i = 0; i < pages.length; i++) {
382 pages[i].setAttribute('aria-hidden', node ? 'true' : 'false');
385 $('overlay').hidden = !node;
387 if (node)
388 ExtensionSettings.focusOverlay();
390 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
391 'stopInterceptingEvents');
394 ExtensionSettings.focusOverlay = function() {
395 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
396 assert(currentlyShowingOverlay);
398 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
399 cr.ui.setInitialFocus(currentlyShowingOverlay);
401 if (!currentlyShowingOverlay.contains(document.activeElement)) {
402 // Make sure focus isn't stuck behind the overlay.
403 document.activeElement.blur();
408 * Utility function to find the width of various UI strings and synchronize
409 * the width of relevant spans. This is crucial for making sure the
410 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
412 function measureCheckboxStrings() {
413 var trashWidth = 30;
414 var measuringDiv = $('font-measuring-div');
415 measuringDiv.textContent =
416 loadTimeData.getString('extensionSettingsEnabled');
417 measuringDiv.className = 'enabled-text';
418 var pxWidth = measuringDiv.clientWidth + trashWidth;
419 measuringDiv.textContent =
420 loadTimeData.getString('extensionSettingsEnable');
421 measuringDiv.className = 'enable-text';
422 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
423 measuringDiv.textContent =
424 loadTimeData.getString('extensionSettingsDeveloperMode');
425 measuringDiv.className = '';
426 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
428 var style = document.createElement('style');
429 style.type = 'text/css';
430 style.textContent =
431 '.enable-checkbox-text {' +
432 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
433 '}' +
434 '#dev-toggle span {' +
435 ' min-width: ' + pxWidth + 'px;' +
436 '}';
437 document.querySelector('head').appendChild(style);
440 // Export
441 return {
442 ExtensionSettings: ExtensionSettings
446 window.addEventListener('load', function(e) {
447 extensions.ExtensionSettings.getInstance().initialize();