Give names to all utility processes.
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extensions.js
blob1eadba3e9c2f6c3ae71f43a665d4527b0dccc7a2
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 * enableAppInfoDialog: boolean,
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 // External Extension installation can be disabled globally, e.g. while a
46 // different overlay is already showing.
47 if (!ExtensionSettings.getInstance().dragEnabled_)
48 return false;
50 // We can't access filenames during the 'dragenter' event, so we have to
51 // wait until 'drop' to decide whether to do something with the file or
52 // not.
53 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
54 return (e.dataTransfer.types &&
55 e.dataTransfer.types.indexOf('Files') > -1);
57 /** @override */
58 doDragEnter: function() {
59 chrome.send('startDrag');
60 ExtensionSettings.showOverlay($('drop-target-overlay'));
62 /** @override */
63 doDragLeave: function() {
64 this.hideDropTargetOverlay_();
65 chrome.send('stopDrag');
67 /** @override */
68 doDragOver: function(e) {
69 e.preventDefault();
71 /** @override */
72 doDrop: function(e) {
73 this.hideDropTargetOverlay_();
74 if (e.dataTransfer.files.length != 1)
75 return;
77 var toSend = null;
78 // Files lack a check if they're a directory, but we can find out through
79 // its item entry.
80 for (var i = 0; i < e.dataTransfer.items.length; ++i) {
81 if (e.dataTransfer.items[i].kind == 'file' &&
82 e.dataTransfer.items[i].webkitGetAsEntry().isDirectory) {
83 toSend = 'installDroppedDirectory';
84 break;
87 // Only process files that look like extensions. Other files should
88 // navigate the browser normally.
89 if (!toSend &&
90 /\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) {
91 toSend = 'installDroppedFile';
94 if (toSend) {
95 e.preventDefault();
96 chrome.send(toSend);
101 * Hide the current overlay if it is the drop target overlay.
102 * @private
104 hideDropTargetOverlay_: function() {
105 var currentOverlay = ExtensionSettings.getCurrentOverlay();
106 if (currentOverlay && currentOverlay.id === 'drop-target-overlay')
107 ExtensionSettings.showOverlay(null);
112 * ExtensionSettings class
113 * @class
115 function ExtensionSettings() {}
117 cr.addSingletonGetter(ExtensionSettings);
119 ExtensionSettings.prototype = {
120 __proto__: HTMLDivElement.prototype,
123 * Whether or not to try to display the Apps Developer Tools promotion.
124 * @type {boolean}
125 * @private
127 displayPromo_: false,
130 * The drag-drop wrapper for installing external Extensions, if available.
131 * null if external Extension installation is not available.
132 * @type {cr.ui.DragWrapper}
133 * @private
135 dragWrapper_: null,
138 * True if drag-drop is both available and currently enabled - it can be
139 * temporarily disabled while overlays are showing.
140 * @type {boolean}
141 * @private
143 dragEnabled_: false,
146 * Perform initial setup.
148 initialize: function() {
149 uber.onContentFrameLoaded();
150 cr.ui.FocusOutlineManager.forDocument(document);
151 measureCheckboxStrings();
153 // Set the title.
154 uber.setTitle(loadTimeData.getString('extensionSettings'));
156 var extensionList = new ExtensionList();
157 extensionList.id = 'extension-settings-list';
158 var wrapper = $('extension-list-wrapper');
159 wrapper.insertBefore(extensionList, wrapper.firstChild);
161 // This will request the data to show on the page and will get a response
162 // back in returnExtensionsData.
163 chrome.send('extensionSettingsRequestExtensionsData');
165 var extensionLoader = extensions.ExtensionLoader.getInstance();
167 $('toggle-dev-on').addEventListener('change', function(e) {
168 this.updateDevControlsVisibility_(true);
169 extensionList.updateFocusableElements();
170 chrome.send('extensionSettingsToggleDeveloperMode',
171 [$('toggle-dev-on').checked]);
172 }.bind(this));
174 window.addEventListener('resize', function() {
175 this.updateDevControlsVisibility_(false);
176 }.bind(this));
178 // Set up the three dev mode buttons (load unpacked, pack and update).
179 $('load-unpacked').addEventListener('click', function(e) {
180 extensionLoader.loadUnpacked();
182 $('pack-extension').addEventListener('click',
183 this.handlePackExtension_.bind(this));
184 $('update-extensions-now').addEventListener('click',
185 this.handleUpdateExtensionNow_.bind(this));
187 // Set up the close dialog for the apps developer tools promo.
188 $('apps-developer-tools-promo').querySelector('.close-button').
189 addEventListener('click', function(e) {
190 this.displayPromo_ = false;
191 this.updateDevControlsVisibility_(true);
192 chrome.send('extensionSettingsDismissADTPromo');
194 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
195 $('update-extensions-now').focus();
196 }.bind(this));
198 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
199 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
200 dragWrapperHandler);
201 this.dragEnabled_ = true;
204 extensions.PackExtensionOverlay.getInstance().initializePage();
206 // Hook up the configure commands link to the overlay.
207 var link = document.querySelector('.extension-commands-config');
208 link.addEventListener('click',
209 this.handleExtensionCommandsConfig_.bind(this));
211 // Initialize the Commands overlay.
212 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
214 extensions.ExtensionErrorOverlay.getInstance().initializePage(
215 extensions.ExtensionSettings.showOverlay);
217 extensions.ExtensionOptionsOverlay.getInstance().initializePage(
218 extensions.ExtensionSettings.showOverlay);
220 // Initialize the kiosk overlay.
221 if (cr.isChromeOS) {
222 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
223 kioskOverlay.initialize();
225 $('add-kiosk-app').addEventListener('click', function() {
226 ExtensionSettings.showOverlay($('kiosk-apps-page'));
227 kioskOverlay.didShowPage();
230 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
233 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
234 cr.ui.overlay.globalInitialization();
236 extensions.ExtensionFocusManager.getInstance().initialize();
238 var path = document.location.pathname;
239 if (path.length > 1) {
240 // Skip starting slash and remove trailing slash (if any).
241 var overlayName = path.slice(1).replace(/\/$/, '');
242 if (overlayName == 'configureCommands')
243 this.showExtensionCommandsConfigUi_();
248 * Handles the Pack Extension button.
249 * @param {Event} e Change event.
250 * @private
252 handlePackExtension_: function(e) {
253 ExtensionSettings.showOverlay($('pack-extension-overlay'));
254 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
258 * Shows the Extension Commands configuration UI.
259 * @param {Event} e Change event.
260 * @private
262 showExtensionCommandsConfigUi_: function(e) {
263 ExtensionSettings.showOverlay($('extension-commands-overlay'));
264 chrome.send('metricsHandler:recordAction',
265 ['Options_ExtensionCommands']);
269 * Handles the Configure (Extension) Commands link.
270 * @param {Event} e Change event.
271 * @private
273 handleExtensionCommandsConfig_: function(e) {
274 this.showExtensionCommandsConfigUi_();
278 * Handles the Update Extension Now button.
279 * @param {Event} e Change event.
280 * @private
282 handleUpdateExtensionNow_: function(e) {
283 chrome.send('extensionSettingsAutoupdate');
287 * Updates the visibility of the developer controls based on whether the
288 * [x] Developer mode checkbox is checked. Also called if a user dismisses
289 * the apps developer tools promo.
290 * @param {boolean} animated Whether to animate any updates.
291 * @private
293 updateDevControlsVisibility_: function(animated) {
294 var showDevControls = $('toggle-dev-on').checked;
295 $('extension-settings').classList.toggle('dev-mode', showDevControls);
297 var devControls = $('dev-controls');
298 devControls.classList.toggle('animated', animated);
300 var buttons = devControls.querySelector('.button-container');
301 var adtPromo = $('apps-developer-tools-promo');
303 {root: buttons, focusable: showDevControls},
304 {root: adtPromo, focusable: showDevControls && this.displayPromo_},
305 ].forEach(function(entry) {
306 var controls = entry.root.querySelectorAll('a, button');
307 Array.prototype.forEach.call(controls, function(control) {
308 control.tabIndex = entry.focusable ? 0 : -1;
310 entry.root.setAttribute('aria-hidden', !entry.focusable);
313 window.requestAnimationFrame(function() {
314 devControls.style.height = !showDevControls ? '' :
315 (this.displayPromo_ ? adtPromo.offsetHeight : 0) +
316 buttons.offsetHeight + 'px';
317 }.bind(this));
322 * Called by the dom_ui_ to re-populate the page with data representing
323 * the current state of installed extensions.
324 * @param {ExtensionDataResponse} extensionsData
326 ExtensionSettings.returnExtensionsData = function(extensionsData) {
327 webuiResponded = true;
329 var supervised = extensionsData.profileIsSupervised;
331 var pageDiv = $('extension-settings');
332 pageDiv.classList.toggle('profile-is-supervised', supervised);
333 pageDiv.classList.toggle('showing-banner', supervised);
335 var devControlsCheckbox = $('toggle-dev-on');
336 devControlsCheckbox.checked = extensionsData.developerMode;
337 devControlsCheckbox.disabled = supervised;
339 var instance = ExtensionSettings.getInstance();
340 instance.displayPromo_ = extensionsData.promoteAppsDevTools;
341 instance.updateDevControlsVisibility_(false);
343 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
344 var extensionList = $('extension-settings-list');
345 extensionList.updateExtensionsData(
346 extensionsData.incognitoAvailable,
347 extensionsData.enableAppInfoDialog).then(function() {
348 // We can get called many times in short order, thus we need to
349 // be careful to remove the 'finished loading' timeout.
350 if (this.loadingTimeout_)
351 window.clearTimeout(this.loadingTimeout_);
352 document.documentElement.classList.add('loading');
353 this.loadingTimeout_ = window.setTimeout(function() {
354 document.documentElement.classList.remove('loading');
355 }, 0);
357 var hasExtensions = extensionList.getNumExtensions() != 0;
358 $('no-extensions').hidden = hasExtensions;
359 $('extension-list-wrapper').hidden = !hasExtensions;
360 $('extension-settings-list').updateFocusableElements();
361 }.bind(this));
365 * Returns the current overlay or null if one does not exist.
366 * @return {Element} The overlay element.
368 ExtensionSettings.getCurrentOverlay = function() {
369 return document.querySelector('#overlay .page.showing');
373 * Sets the given overlay to show. If the overlay is already showing, this is
374 * a no-op; otherwise, hides any currently-showing overlay.
375 * @param {HTMLElement} node The overlay page to show. If null, all overlays
376 * are hidden.
378 ExtensionSettings.showOverlay = function(node) {
379 var pageDiv = $('extension-settings');
380 pageDiv.style.width = node ? window.getComputedStyle(pageDiv).width : '';
381 document.body.classList.toggle('no-scroll', !!node);
383 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
384 if (currentlyShowingOverlay) {
385 if (currentlyShowingOverlay == node) // Already displayed.
386 return;
387 currentlyShowingOverlay.classList.remove('showing');
388 cr.dispatchSimpleEvent($('overlay'), 'cancelOverlay');
391 if (node) {
392 var lastFocused = document.activeElement;
393 $('overlay').addEventListener('cancelOverlay', function f() {
394 lastFocused.focus();
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;
407 if (node)
408 ExtensionSettings.focusOverlay();
410 // If drag-drop for external Extension installation is available, enable
411 // drag-drop when there is any overlay showing other than the usual overlay
412 // shown when drag-drop is started.
413 var settings = ExtensionSettings.getInstance();
414 if (settings.dragWrapper_)
415 settings.dragEnabled_ = !node || node == $('drop-target-overlay');
417 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
418 'stopInterceptingEvents');
421 ExtensionSettings.focusOverlay = function() {
422 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
423 assert(currentlyShowingOverlay);
425 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
426 cr.ui.setInitialFocus(currentlyShowingOverlay);
428 if (!currentlyShowingOverlay.contains(document.activeElement)) {
429 // Make sure focus isn't stuck behind the overlay.
430 document.activeElement.blur();
435 * Utility function to find the width of various UI strings and synchronize
436 * the width of relevant spans. This is crucial for making sure the
437 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
439 function measureCheckboxStrings() {
440 var trashWidth = 30;
441 var measuringDiv = $('font-measuring-div');
442 measuringDiv.textContent =
443 loadTimeData.getString('extensionSettingsEnabled');
444 measuringDiv.className = 'enabled-text';
445 var pxWidth = measuringDiv.clientWidth + trashWidth;
446 measuringDiv.textContent =
447 loadTimeData.getString('extensionSettingsEnable');
448 measuringDiv.className = 'enable-text';
449 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
450 measuringDiv.textContent =
451 loadTimeData.getString('extensionSettingsDeveloperMode');
452 measuringDiv.className = '';
453 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
455 var style = document.createElement('style');
456 style.type = 'text/css';
457 style.textContent =
458 '.enable-checkbox-text {' +
459 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
460 '}' +
461 '#dev-toggle span {' +
462 ' min-width: ' + pxWidth + 'px;' +
463 '}';
464 document.querySelector('head').appendChild(style);
467 // Export
468 return {
469 ExtensionSettings: ExtensionSettings
473 window.addEventListener('load', function(e) {
474 extensions.ExtensionSettings.getInstance().initialize();