[sql] Remove _HAS_EXCEPTIONS=0 from build info.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extensions.js
blob863d2fe6b608c1fd3af5a1cff94235358423dc6a
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 // Used for observing function of the backend datasource for this page by
22 // tests.
23 var webuiResponded = false;
25 cr.define('extensions', function() {
26 var ExtensionList = extensions.ExtensionList;
28 // Implements the DragWrapper handler interface.
29 var dragWrapperHandler = {
30 /** @override */
31 shouldAcceptDrag: function(e) {
32 // External Extension installation can be disabled globally, e.g. while a
33 // different overlay is already showing.
34 if (!ExtensionSettings.getInstance().dragEnabled_)
35 return false;
37 // We can't access filenames during the 'dragenter' event, so we have to
38 // wait until 'drop' to decide whether to do something with the file or
39 // not.
40 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
41 return (e.dataTransfer.types &&
42 e.dataTransfer.types.indexOf('Files') > -1);
44 /** @override */
45 doDragEnter: function() {
46 chrome.send('startDrag');
47 ExtensionSettings.showOverlay($('drop-target-overlay'));
49 /** @override */
50 doDragLeave: function() {
51 this.hideDropTargetOverlay_();
52 chrome.send('stopDrag');
54 /** @override */
55 doDragOver: function(e) {
56 e.preventDefault();
58 /** @override */
59 doDrop: function(e) {
60 this.hideDropTargetOverlay_();
61 if (e.dataTransfer.files.length != 1)
62 return;
64 var toSend = null;
65 // Files lack a check if they're a directory, but we can find out through
66 // its item entry.
67 for (var i = 0; i < e.dataTransfer.items.length; ++i) {
68 if (e.dataTransfer.items[i].kind == 'file' &&
69 e.dataTransfer.items[i].webkitGetAsEntry().isDirectory) {
70 toSend = 'installDroppedDirectory';
71 break;
74 // Only process files that look like extensions. Other files should
75 // navigate the browser normally.
76 if (!toSend &&
77 /\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) {
78 toSend = 'installDroppedFile';
81 if (toSend) {
82 e.preventDefault();
83 chrome.send(toSend);
87 /**
88 * Hide the current overlay if it is the drop target overlay.
89 * @private
91 hideDropTargetOverlay_: function() {
92 var currentOverlay = ExtensionSettings.getCurrentOverlay();
93 if (currentOverlay && currentOverlay.id === 'drop-target-overlay')
94 ExtensionSettings.showOverlay(null);
98 /**
99 * ExtensionSettings class
100 * @class
101 * @constructor
102 * @implements {extensions.ExtensionListDelegate}
104 function ExtensionSettings() {}
106 cr.addSingletonGetter(ExtensionSettings);
108 ExtensionSettings.prototype = {
109 __proto__: HTMLDivElement.prototype,
112 * The drag-drop wrapper for installing external Extensions, if available.
113 * null if external Extension installation is not available.
114 * @type {cr.ui.DragWrapper}
115 * @private
117 dragWrapper_: null,
120 * True if drag-drop is both available and currently enabled - it can be
121 * temporarily disabled while overlays are showing.
122 * @type {boolean}
123 * @private
125 dragEnabled_: false,
128 * Perform initial setup.
130 initialize: function() {
131 this.setLoading_(true);
132 uber.onContentFrameLoaded();
133 cr.ui.FocusOutlineManager.forDocument(document);
134 measureCheckboxStrings();
136 // Set the title.
137 uber.setTitle(loadTimeData.getString('extensionSettings'));
139 var extensionList = new ExtensionList(this);
140 extensionList.id = 'extension-settings-list';
141 var wrapper = $('extension-list-wrapper');
142 wrapper.insertBefore(extensionList, wrapper.firstChild);
144 // Get the initial profile state, and register to be notified of any
145 // future changes.
146 chrome.developerPrivate.getProfileConfiguration(
147 this.update_.bind(this));
148 chrome.developerPrivate.onProfileStateChanged.addListener(
149 this.update_.bind(this));
151 var extensionLoader = extensions.ExtensionLoader.getInstance();
153 $('toggle-dev-on').addEventListener('change', function(e) {
154 this.updateDevControlsVisibility_(true);
155 extensionList.updateFocusableElements();
156 chrome.developerPrivate.updateProfileConfiguration(
157 {inDeveloperMode: e.target.checked});
158 var suffix = $('toggle-dev-on').checked ? 'Enabled' : 'Disabled';
159 chrome.send('metricsHandler:recordAction',
160 ['Options_ToggleDeveloperMode_' + suffix]);
161 }.bind(this));
163 window.addEventListener('resize', function() {
164 this.updateDevControlsVisibility_(false);
165 }.bind(this));
167 // Set up the three dev mode buttons (load unpacked, pack and update).
168 $('load-unpacked').addEventListener('click', function(e) {
169 chrome.send('metricsHandler:recordAction',
170 ['Options_LoadUnpackedExtension']);
171 extensionLoader.loadUnpacked();
173 $('pack-extension').addEventListener('click',
174 this.handlePackExtension_.bind(this));
175 $('update-extensions-now').addEventListener('click',
176 this.handleUpdateExtensionNow_.bind(this));
178 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
179 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
180 dragWrapperHandler);
181 this.dragEnabled_ = true;
184 extensions.PackExtensionOverlay.getInstance().initializePage();
186 // Hook up the configure commands link to the overlay.
187 var link = document.querySelector('.extension-commands-config');
188 link.addEventListener('click',
189 this.handleExtensionCommandsConfig_.bind(this));
191 // Initialize the Commands overlay.
192 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
194 extensions.ExtensionErrorOverlay.getInstance().initializePage(
195 extensions.ExtensionSettings.showOverlay);
197 extensions.ExtensionOptionsOverlay.getInstance().initializePage(
198 extensions.ExtensionSettings.showOverlay);
200 // Add user action logging for bottom links.
201 var moreExtensionLink =
202 document.getElementsByClassName('more-extensions-link');
203 for (var i = 0; i < moreExtensionLink.length; i++) {
204 moreExtensionLink[i].addEventListener('click', function(e) {
205 chrome.send('metricsHandler:recordAction',
206 ['Options_GetMoreExtensions']);
210 // Initialize the kiosk overlay.
211 if (cr.isChromeOS) {
212 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
213 kioskOverlay.initialize();
215 $('add-kiosk-app').addEventListener('click', function() {
216 ExtensionSettings.showOverlay($('kiosk-apps-page'));
217 kioskOverlay.didShowPage();
220 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
223 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
224 cr.ui.overlay.globalInitialization();
226 extensions.ExtensionFocusManager.getInstance().initialize();
228 var path = document.location.pathname;
229 if (path.length > 1) {
230 // Skip starting slash and remove trailing slash (if any).
231 var overlayName = path.slice(1).replace(/\/$/, '');
232 if (overlayName == 'configureCommands')
233 this.showExtensionCommandsConfigUi_();
238 * [Re]-Populates the page with data representing the current state of
239 * installed extensions.
240 * @param {ProfileInfo} profileInfo
241 * @private
243 update_: function(profileInfo) {
244 this.setLoading_(true);
245 webuiResponded = true;
247 /** @const */
248 var supervised = profileInfo.isSupervised;
250 var pageDiv = $('extension-settings');
251 pageDiv.classList.toggle('profile-is-supervised', supervised);
252 pageDiv.classList.toggle('showing-banner', supervised);
254 var devControlsCheckbox = $('toggle-dev-on');
255 devControlsCheckbox.checked = profileInfo.inDeveloperMode;
256 devControlsCheckbox.disabled = supervised;
258 $('load-unpacked').disabled = !profileInfo.canLoadUnpacked;
259 var extensionList = $('extension-settings-list');
260 extensionList.updateExtensionsData(
261 profileInfo.isIncognitoAvailable,
262 profileInfo.appInfoDialogEnabled).then(function() {
263 this.setLoading_(false);
264 this.onExtensionCountChanged();
265 }.bind(this));
269 * Shows the loading spinner and hides elements that shouldn't be visible
270 * while loading.
271 * @param {boolean} isLoading
272 * @private
274 setLoading_: function(isLoading) {
275 document.documentElement.classList.toggle('loading', isLoading);
276 $('loading-spinner').hidden = !isLoading;
277 $('dev-controls').hidden = isLoading;
278 this.updateDevControlsVisibility_(false);
280 // The extension list is already hidden/shown elsewhere and shouldn't be
281 // updated here because it can be hidden if there are no extensions.
285 * Handles the Pack Extension button.
286 * @param {Event} e Change event.
287 * @private
289 handlePackExtension_: function(e) {
290 ExtensionSettings.showOverlay($('pack-extension-overlay'));
291 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
295 * Shows the Extension Commands configuration UI.
296 * @private
298 showExtensionCommandsConfigUi_: function() {
299 ExtensionSettings.showOverlay($('extension-commands-overlay'));
300 chrome.send('metricsHandler:recordAction',
301 ['Options_ExtensionCommands']);
305 * Handles the Configure (Extension) Commands link.
306 * @param {Event} e Change event.
307 * @private
309 handleExtensionCommandsConfig_: function(e) {
310 this.showExtensionCommandsConfigUi_();
314 * Handles the Update Extension Now button.
315 * @param {Event} e Change event.
316 * @private
318 handleUpdateExtensionNow_: function(e) {
319 chrome.developerPrivate.autoUpdate();
320 chrome.send('metricsHandler:recordAction',
321 ['Options_UpdateExtensions']);
325 * Updates the visibility of the developer controls based on whether the
326 * [x] Developer mode checkbox is checked.
327 * @param {boolean} animated Whether to animate any updates.
328 * @private
330 updateDevControlsVisibility_: function(animated) {
331 var showDevControls = $('toggle-dev-on').checked;
332 $('extension-settings').classList.toggle('dev-mode', showDevControls);
334 var devControls = $('dev-controls');
335 devControls.classList.toggle('animated', animated);
337 var buttons = devControls.querySelector('.button-container');
338 Array.prototype.forEach.call(buttons.querySelectorAll('a, button'),
339 function(control) {
340 control.tabIndex = showDevControls ? 0 : -1;
342 buttons.setAttribute('aria-hidden', !showDevControls);
344 window.requestAnimationFrame(function() {
345 devControls.style.height = !showDevControls ? '' :
346 buttons.offsetHeight + 'px';
348 document.dispatchEvent(new Event('devControlsVisibilityUpdated'));
349 }.bind(this));
352 /** @override */
353 onExtensionCountChanged: function() {
354 /** @const */
355 var hasExtensions = $('extension-settings-list').getNumExtensions() != 0;
356 $('no-extensions').hidden = hasExtensions;
357 $('extension-list-wrapper').hidden = !hasExtensions;
362 * Returns the current overlay or null if one does not exist.
363 * @return {Element} The overlay element.
365 ExtensionSettings.getCurrentOverlay = function() {
366 return document.querySelector('#overlay .page.showing');
370 * Sets the given overlay to show. If the overlay is already showing, this is
371 * a no-op; otherwise, hides any currently-showing overlay.
372 * @param {HTMLElement} node The overlay page to show. If null, all overlays
373 * are hidden.
375 ExtensionSettings.showOverlay = function(node) {
376 var pageDiv = $('extension-settings');
377 pageDiv.style.width = node ? window.getComputedStyle(pageDiv).width : '';
378 document.body.classList.toggle('no-scroll', !!node);
380 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
381 if (currentlyShowingOverlay) {
382 if (currentlyShowingOverlay == node) // Already displayed.
383 return;
384 currentlyShowingOverlay.classList.remove('showing');
387 if (node) {
388 var lastFocused;
390 var focusOutlineManager = cr.ui.FocusOutlineManager.forDocument(document);
391 if (focusOutlineManager.visible)
392 lastFocused = document.activeElement;
394 $('overlay').addEventListener('cancelOverlay', function f() {
395 console.log('cancelOverlay');
396 console.log('lastFocused', lastFocused);
397 console.log('focusOutlineManager.visible', focusOutlineManager.visible);
398 if (lastFocused && focusOutlineManager.visible)
399 lastFocused.focus();
401 $('overlay').removeEventListener('cancelOverlay', f);
403 node.classList.add('showing');
406 var pages = document.querySelectorAll('.page');
407 for (var i = 0; i < pages.length; i++) {
408 pages[i].setAttribute('aria-hidden', node ? 'true' : 'false');
411 $('overlay').hidden = !node;
413 if (node)
414 ExtensionSettings.focusOverlay();
416 // If drag-drop for external Extension installation is available, enable
417 // drag-drop when there is any overlay showing other than the usual overlay
418 // shown when drag-drop is started.
419 var settings = ExtensionSettings.getInstance();
420 if (settings.dragWrapper_)
421 settings.dragEnabled_ = !node || node == $('drop-target-overlay');
423 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
424 'stopInterceptingEvents');
427 ExtensionSettings.focusOverlay = function() {
428 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
429 assert(currentlyShowingOverlay);
431 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
432 cr.ui.setInitialFocus(currentlyShowingOverlay);
434 if (!currentlyShowingOverlay.contains(document.activeElement)) {
435 // Make sure focus isn't stuck behind the overlay.
436 document.activeElement.blur();
441 * Utility function to find the width of various UI strings and synchronize
442 * the width of relevant spans. This is crucial for making sure the
443 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
445 function measureCheckboxStrings() {
446 var trashWidth = 30;
447 var measuringDiv = $('font-measuring-div');
448 measuringDiv.textContent =
449 loadTimeData.getString('extensionSettingsEnabled');
450 measuringDiv.className = 'enabled-text';
451 var pxWidth = measuringDiv.clientWidth + trashWidth;
452 measuringDiv.textContent =
453 loadTimeData.getString('extensionSettingsEnable');
454 measuringDiv.className = 'enable-text';
455 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
456 measuringDiv.textContent =
457 loadTimeData.getString('extensionSettingsDeveloperMode');
458 measuringDiv.className = '';
459 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
461 var style = document.createElement('style');
462 style.type = 'text/css';
463 style.textContent =
464 '.enable-checkbox-text {' +
465 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
466 '}' +
467 '#dev-toggle span {' +
468 ' min-width: ' + pxWidth + 'px;' +
469 '}';
470 document.querySelector('head').appendChild(style);
473 // Export
474 return {
475 ExtensionSettings: ExtensionSettings
479 window.addEventListener('load', function(e) {
480 extensions.ExtensionSettings.getInstance().initialize();