Extract code handling PrinterProviderAPI from PrintPreviewHandler
[chromium-blink-merge.git] / chrome / browser / resources / extensions / extensions.js
blob65a0fc6dd99c8a3e7423c08a6ceb47e6c3ac82fc
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">
15 <if expr="chromeos">
16 <include src="chromeos/kiosk_apps.js">
17 </if>
19 /**
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,
24 * extensions: Array,
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
33 // tests.
34 var webuiResponded = false;
36 cr.define('extensions', function() {
37 var ExtensionsList = options.ExtensionsList;
39 // Implements the DragWrapper handler interface.
40 var dragWrapperHandler = {
41 /** @override */
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
45 // not.
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);
50 /** @override */
51 doDragEnter: function() {
52 chrome.send('startDrag');
53 ExtensionSettings.showOverlay(null);
54 ExtensionSettings.showOverlay($('drop-target-overlay'));
56 /** @override */
57 doDragLeave: function() {
58 this.hideDropTargetOverlay_();
59 chrome.send('stopDrag');
61 /** @override */
62 doDragOver: function(e) {
63 e.preventDefault();
65 /** @override */
66 doDrop: function(e) {
67 this.hideDropTargetOverlay_();
68 if (e.dataTransfer.files.length != 1)
69 return;
71 var toSend = null;
72 // Files lack a check if they're a directory, but we can find out through
73 // its item entry.
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';
78 break;
81 // Only process files that look like extensions. Other files should
82 // navigate the browser normally.
83 if (!toSend &&
84 /\.(crx|user\.js|zip)$/i.test(e.dataTransfer.files[0].name)) {
85 toSend = 'installDroppedFile';
88 if (toSend) {
89 e.preventDefault();
90 chrome.send(toSend);
94 /**
95 * Hide the current overlay if it is the drop target overlay.
96 * @private
98 hideDropTargetOverlay_: function() {
99 var currentOverlay = ExtensionSettings.getCurrentOverlay();
100 if (currentOverlay && currentOverlay.id === 'drop-target-overlay')
101 ExtensionSettings.showOverlay(null);
106 * ExtensionSettings class
107 * @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.
118 * @type {boolean}
119 * @private
121 displayPromo_: false,
124 * Perform initial setup.
126 initialize: function() {
127 uber.onContentFrameLoaded();
128 cr.ui.FocusOutlineManager.forDocument(document);
129 measureCheckboxStrings();
131 // Set the title.
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', function(e) {
141 this.updateDevControlsVisibility_(true);
142 chrome.send('extensionSettingsToggleDeveloperMode',
143 [$('toggle-dev-on').checked]);
144 }.bind(this));
146 window.addEventListener('resize', function() {
147 this.updateDevControlsVisibility_(false);
148 }.bind(this));
150 // Set up the three dev mode buttons (load unpacked, pack and update).
151 $('load-unpacked').addEventListener('click', function(e) {
152 extensionLoader.loadUnpacked();
154 $('pack-extension').addEventListener('click',
155 this.handlePackExtension_.bind(this));
156 $('update-extensions-now').addEventListener('click',
157 this.handleUpdateExtensionNow_.bind(this));
159 // Set up the close dialog for the apps developer tools promo.
160 $('apps-developer-tools-promo').querySelector('.close-button').
161 addEventListener('click', function(e) {
162 this.displayPromo_ = false;
163 this.updateDevControlsVisibility_(true);
164 chrome.send('extensionSettingsDismissADTPromo');
166 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
167 $('update-extensions-now').focus();
168 }.bind(this));
170 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
171 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
172 dragWrapperHandler);
175 extensions.PackExtensionOverlay.getInstance().initializePage();
177 // Hook up the configure commands link to the overlay.
178 var link = document.querySelector('.extension-commands-config');
179 link.addEventListener('click',
180 this.handleExtensionCommandsConfig_.bind(this));
182 // Initialize the Commands overlay.
183 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
185 extensions.ExtensionErrorOverlay.getInstance().initializePage(
186 extensions.ExtensionSettings.showOverlay);
188 extensions.ExtensionOptionsOverlay.getInstance().initializePage(
189 extensions.ExtensionSettings.showOverlay);
191 // Initialize the kiosk overlay.
192 if (cr.isChromeOS) {
193 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
194 kioskOverlay.initialize();
196 $('add-kiosk-app').addEventListener('click', function() {
197 ExtensionSettings.showOverlay($('kiosk-apps-page'));
198 kioskOverlay.didShowPage();
201 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
204 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
205 cr.ui.overlay.globalInitialization();
207 extensions.ExtensionFocusManager.getInstance().initialize();
209 var path = document.location.pathname;
210 if (path.length > 1) {
211 // Skip starting slash and remove trailing slash (if any).
212 var overlayName = path.slice(1).replace(/\/$/, '');
213 if (overlayName == 'configureCommands')
214 this.showExtensionCommandsConfigUi_();
219 * Handles the Pack Extension button.
220 * @param {Event} e Change event.
221 * @private
223 handlePackExtension_: function(e) {
224 ExtensionSettings.showOverlay($('pack-extension-overlay'));
225 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
229 * Shows the Extension Commands configuration UI.
230 * @param {Event} e Change event.
231 * @private
233 showExtensionCommandsConfigUi_: function(e) {
234 ExtensionSettings.showOverlay($('extension-commands-overlay'));
235 chrome.send('metricsHandler:recordAction',
236 ['Options_ExtensionCommands']);
240 * Handles the Configure (Extension) Commands link.
241 * @param {Event} e Change event.
242 * @private
244 handleExtensionCommandsConfig_: function(e) {
245 this.showExtensionCommandsConfigUi_();
249 * Handles the Update Extension Now button.
250 * @param {Event} e Change event.
251 * @private
253 handleUpdateExtensionNow_: function(e) {
254 chrome.send('extensionSettingsAutoupdate');
258 * Updates the visibility of the developer controls based on whether the
259 * [x] Developer mode checkbox is checked. Also called if a user dismisses
260 * the apps developer tools promo.
261 * @param {boolean} animated Whether to animate any updates.
262 * @private
264 updateDevControlsVisibility_: function(animated) {
265 var showDevControls = $('toggle-dev-on').checked;
266 $('extension-settings').classList.toggle('dev-mode', showDevControls);
268 var devControls = $('dev-controls');
269 devControls.classList.toggle('animated', animated);
271 var buttons = devControls.querySelector('.button-container');
272 var adtPromo = $('apps-developer-tools-promo');
274 {root: buttons, focusable: showDevControls},
275 {root: adtPromo, focusable: showDevControls && this.displayPromo_},
276 ].forEach(function(entry) {
277 var controls = entry.root.querySelectorAll('a, button');
278 Array.prototype.forEach.call(controls, function(control) {
279 control.tabIndex = entry.focusable ? 0 : -1;
281 entry.root.setAttribute('aria-hidden', !entry.focusable);
284 window.requestAnimationFrame(function() {
285 devControls.style.height = !showDevControls ? '' :
286 (this.displayPromo_ ? adtPromo.offsetHeight : 0) +
287 buttons.offsetHeight + 'px';
288 }.bind(this));
293 * Called by the dom_ui_ to re-populate the page with data representing
294 * the current state of installed extensions.
295 * @param {ExtensionDataResponse} extensionsData
297 ExtensionSettings.returnExtensionsData = function(extensionsData) {
298 // We can get called many times in short order, thus we need to
299 // be careful to remove the 'finished loading' timeout.
300 if (this.loadingTimeout_)
301 window.clearTimeout(this.loadingTimeout_);
302 document.documentElement.classList.add('loading');
303 this.loadingTimeout_ = window.setTimeout(function() {
304 document.documentElement.classList.remove('loading');
305 }, 0);
307 webuiResponded = true;
309 if (extensionsData.extensions.length > 0) {
310 // Enforce order specified in the data or (if equal) then sort by
311 // extension name (case-insensitive) followed by their ID (in the case
312 // where extensions have the same name).
313 extensionsData.extensions.sort(function(a, b) {
314 function compare(x, y) {
315 return x < y ? -1 : (x > y ? 1 : 0);
317 return compare(a.order, b.order) ||
318 compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
319 compare(a.id, b.id);
323 var supervised = extensionsData.profileIsSupervised;
325 var pageDiv = $('extension-settings');
326 pageDiv.classList.toggle('profile-is-supervised', supervised);
327 pageDiv.classList.toggle('showing-banner', supervised);
329 var devControlsCheckbox = $('toggle-dev-on');
330 devControlsCheckbox.checked = extensionsData.developerMode;
331 devControlsCheckbox.disabled = supervised;
333 var instance = ExtensionSettings.getInstance();
334 instance.displayPromo_ = extensionsData.promoteAppsDevTools;
335 instance.updateDevControlsVisibility_(false);
337 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
339 ExtensionsList.prototype.data_ = extensionsData;
340 var extensionList = $('extension-settings-list');
341 ExtensionsList.decorate(extensionList);
344 // Indicate that warning |message| has occured for pack of |crx_path| and
345 // |pem_path| files. Ask if user wants override the warning. Send
346 // |overrideFlags| to repeated 'pack' call to accomplish the override.
347 ExtensionSettings.askToOverrideWarning =
348 function(message, crx_path, pem_path, overrideFlags) {
349 var closeAlert = function() {
350 ExtensionSettings.showOverlay(null);
353 alertOverlay.setValues(
354 loadTimeData.getString('packExtensionWarningTitle'),
355 message,
356 loadTimeData.getString('packExtensionProceedAnyway'),
357 loadTimeData.getString('cancel'),
358 function() {
359 chrome.send('pack', [crx_path, pem_path, overrideFlags]);
360 closeAlert();
362 closeAlert);
363 ExtensionSettings.showOverlay($('alertOverlay'));
367 * Returns the current overlay or null if one does not exist.
368 * @return {Element} The overlay element.
370 ExtensionSettings.getCurrentOverlay = function() {
371 return document.querySelector('#overlay .page.showing');
375 * Sets the given overlay to show. This hides whatever overlay is currently
376 * showing, if any.
377 * @param {HTMLElement} node The overlay page to show. If null, all overlays
378 * are hidden.
380 ExtensionSettings.showOverlay = function(node) {
381 var pageDiv = $('extension-settings');
382 pageDiv.style.width = node ? window.getComputedStyle(pageDiv).width : '';
383 document.body.classList.toggle('no-scroll', !!node);
385 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
386 if (currentlyShowingOverlay) {
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 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
411 'stopInterceptingEvents');
414 ExtensionSettings.focusOverlay = function() {
415 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
416 assert(currentlyShowingOverlay);
418 if (cr.ui.FocusOutlineManager.forDocument(document).visible)
419 cr.ui.setInitialFocus(currentlyShowingOverlay);
421 if (!currentlyShowingOverlay.contains(document.activeElement)) {
422 // Make sure focus isn't stuck behind the overlay.
423 document.activeElement.blur();
428 * Utility function to find the width of various UI strings and synchronize
429 * the width of relevant spans. This is crucial for making sure the
430 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
432 function measureCheckboxStrings() {
433 var trashWidth = 30;
434 var measuringDiv = $('font-measuring-div');
435 measuringDiv.textContent =
436 loadTimeData.getString('extensionSettingsEnabled');
437 measuringDiv.className = 'enabled-text';
438 var pxWidth = measuringDiv.clientWidth + trashWidth;
439 measuringDiv.textContent =
440 loadTimeData.getString('extensionSettingsEnable');
441 measuringDiv.className = 'enable-text';
442 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
443 measuringDiv.textContent =
444 loadTimeData.getString('extensionSettingsDeveloperMode');
445 measuringDiv.className = '';
446 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
448 var style = document.createElement('style');
449 style.type = 'text/css';
450 style.textContent =
451 '.enable-checkbox-text {' +
452 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
453 '}' +
454 '#dev-toggle span {' +
455 ' min-width: ' + pxWidth + 'px;' +
456 '}';
457 document.querySelector('head').appendChild(style);
460 // Export
461 return {
462 ExtensionSettings: ExtensionSettings
466 window.addEventListener('load', function(e) {
467 extensions.ExtensionSettings.getInstance().initialize();