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"></include>
6 <include src="extension_commands_overlay.js"></include>
7 <include src="extension_focus_manager.js"></include>
8 <include src="extension_list.js"></include>
9 <include src="pack_extension_overlay.js"></include>
10 <include src="extension_error_overlay.js"></include>
12 <if expr="pp_ifdef('chromeos')">
13 <include src="chromeos/kiosk_apps.js"></include>
16 // Used for observing function of the backend datasource for this page by
18 var webuiResponded = false;
20 cr.define('extensions', function() {
21 var ExtensionsList = options.ExtensionsList;
23 // Implements the DragWrapper handler interface.
24 var dragWrapperHandler = {
26 shouldAcceptDrag: function(e) {
27 // We can't access filenames during the 'dragenter' event, so we have to
28 // wait until 'drop' to decide whether to do something with the file or
30 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p
31 return (e.dataTransfer.types &&
32 e.dataTransfer.types.indexOf('Files') > -1);
35 doDragEnter: function() {
36 chrome.send('startDrag');
37 ExtensionSettings.showOverlay(null);
38 ExtensionSettings.showOverlay($('drop-target-overlay'));
41 doDragLeave: function() {
42 ExtensionSettings.showOverlay(null);
43 chrome.send('stopDrag');
46 doDragOver: function(e) {
51 ExtensionSettings.showOverlay(null);
52 if (e.dataTransfer.files.length != 1)
56 // Files lack a check if they're a directory, but we can find out through
58 for (var i = 0; i < e.dataTransfer.items.length; ++i) {
59 if (e.dataTransfer.items[i].kind == 'file' &&
60 e.dataTransfer.items[i].webkitGetAsEntry().isDirectory) {
61 toSend = 'installDroppedDirectory';
65 // Only process files that look like extensions. Other files should
66 // navigate the browser normally.
67 if (!toSend && /\.(crx|user\.js)$/i.test(e.dataTransfer.files[0].name))
68 toSend = 'installDroppedFile';
78 * ExtensionSettings class
81 function ExtensionSettings() {}
83 cr.addSingletonGetter(ExtensionSettings);
85 ExtensionSettings.prototype = {
86 __proto__: HTMLDivElement.prototype,
89 * Perform initial setup.
91 initialize: function() {
92 uber.onContentFrameLoaded();
93 cr.ui.FocusOutlineManager.forDocument(document);
94 measureCheckboxStrings();
97 var title = loadTimeData.getString('extensionSettings');
98 uber.invokeMethodOnParent('setTitle', {title: title});
100 // This will request the data to show on the page and will get a response
101 // back in returnExtensionsData.
102 chrome.send('extensionSettingsRequestExtensionsData');
104 $('toggle-dev-on').addEventListener('change',
105 this.handleToggleDevMode_.bind(this));
106 $('dev-controls').addEventListener('webkitTransitionEnd',
107 this.handleDevControlsTransitionEnd_.bind(this));
108 $('open-apps-dev-tools').addEventListener('click',
109 this.handleOpenAppsDevTools_.bind(this));
111 // Set up the three dev mode buttons (load unpacked, pack and update).
112 $('load-unpacked').addEventListener('click',
113 this.handleLoadUnpackedExtension_.bind(this));
114 $('pack-extension').addEventListener('click',
115 this.handlePackExtension_.bind(this));
116 $('update-extensions-now').addEventListener('click',
117 this.handleUpdateExtensionNow_.bind(this));
119 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
120 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
124 extensions.PackExtensionOverlay.getInstance().initializePage();
126 // Hook up the configure commands link to the overlay.
127 var link = document.querySelector('.extension-commands-config');
128 link.addEventListener('click',
129 this.handleExtensionCommandsConfig_.bind(this));
131 // Initialize the Commands overlay.
132 extensions.ExtensionCommandsOverlay.getInstance().initializePage();
134 extensions.ExtensionErrorOverlay.getInstance().initializePage();
136 // Initialize the kiosk overlay.
138 var kioskOverlay = extensions.KioskAppsOverlay.getInstance();
139 kioskOverlay.initialize();
141 $('add-kiosk-app').addEventListener('click', function() {
142 ExtensionSettings.showOverlay($('kiosk-apps-page'));
143 kioskOverlay.didShowPage();
146 extensions.KioskDisableBailoutConfirm.getInstance().initialize();
149 cr.ui.overlay.setupOverlay($('drop-target-overlay'));
150 cr.ui.overlay.globalInitialization();
152 extensions.ExtensionFocusManager.getInstance().initialize();
154 var path = document.location.pathname;
155 if (path.length > 1) {
156 // Skip starting slash and remove trailing slash (if any).
157 var overlayName = path.slice(1).replace(/\/$/, '');
158 if (overlayName == 'configureCommands')
159 this.showExtensionCommandsConfigUi_();
162 preventDefaultOnPoundLinkClicks(); // From webui/js/util.js.
166 * Handles the Load Unpacked Extension button.
167 * @param {Event} e Change event.
170 handleLoadUnpackedExtension_: function(e) {
171 chrome.send('extensionSettingsLoadUnpackedExtension');
175 * Handles the Pack Extension button.
176 * @param {Event} e Change event.
179 handlePackExtension_: function(e) {
180 ExtensionSettings.showOverlay($('pack-extension-overlay'));
181 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']);
185 * Shows the Extension Commands configuration UI.
186 * @param {Event} e Change event.
189 showExtensionCommandsConfigUi_: function(e) {
190 ExtensionSettings.showOverlay($('extension-commands-overlay'));
191 chrome.send('metricsHandler:recordAction',
192 ['Options_ExtensionCommands']);
196 * Handles the Configure (Extension) Commands link.
197 * @param {Event} e Change event.
200 handleExtensionCommandsConfig_: function(e) {
201 this.showExtensionCommandsConfigUi_();
205 * Handles the Update Extension Now button.
206 * @param {Event} e Change event.
209 handleUpdateExtensionNow_: function(e) {
210 chrome.send('extensionSettingsAutoupdate');
214 * Handles the Toggle Dev Mode button.
215 * @param {Event} e Change event.
218 handleToggleDevMode_: function(e) {
219 if ($('toggle-dev-on').checked) {
220 $('dev-controls').hidden = false;
221 window.setTimeout(function() {
222 $('extension-settings').classList.add('dev-mode');
225 $('extension-settings').classList.remove('dev-mode');
228 chrome.send('extensionSettingsToggleDeveloperMode');
232 * Called when a transition has ended for #dev-controls.
233 * @param {Event} e webkitTransitionEnd event.
236 handleDevControlsTransitionEnd_: function(e) {
237 if (e.propertyName == 'height' &&
238 !$('extension-settings').classList.contains('dev-mode')) {
239 $('dev-controls').hidden = true;
244 * Called when the user clicked on the button to launch Apps Developer
246 * @param {!Event} e A click event.
249 handleOpenAppsDevTools_: function(e) {
250 chrome.send('extensionSettingsLaunch',
251 ['lphgohfeebnhcpiohjndkgbhhkoapkjc']);
256 * Called by the dom_ui_ to re-populate the page with data representing
257 * the current state of installed extensions.
259 ExtensionSettings.returnExtensionsData = function(extensionsData) {
260 // We can get called many times in short order, thus we need to
261 // be careful to remove the 'finished loading' timeout.
262 if (this.loadingTimeout_)
263 window.clearTimeout(this.loadingTimeout_);
264 document.documentElement.classList.add('loading');
265 this.loadingTimeout_ = window.setTimeout(function() {
266 document.documentElement.classList.remove('loading');
269 webuiResponded = true;
271 if (extensionsData.extensions.length > 0) {
272 // Enforce order specified in the data or (if equal) then sort by
273 // extension name (case-insensitive) followed by their ID (in the case
274 // where extensions have the same name).
275 extensionsData.extensions.sort(function(a, b) {
276 function compare(x, y) {
277 return x < y ? -1 : (x > y ? 1 : 0);
279 return compare(a.order, b.order) ||
280 compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
285 var pageDiv = $('extension-settings');
287 if (extensionsData.profileIsManaged) {
288 pageDiv.classList.add('profile-is-managed');
290 pageDiv.classList.remove('profile-is-managed');
292 if (extensionsData.profileIsManaged) {
293 pageDiv.classList.add('showing-banner');
294 $('toggle-dev-on').disabled = true;
297 pageDiv.classList.remove('showing-banner');
298 $('toggle-dev-on').disabled = false;
301 pageDiv.style.marginTop = marginTop + 'px';
303 if (extensionsData.developerMode) {
304 pageDiv.classList.add('dev-mode');
305 $('toggle-dev-on').checked = true;
306 $('dev-controls').hidden = false;
308 pageDiv.classList.remove('dev-mode');
309 $('toggle-dev-on').checked = false;
312 if (extensionsData.appsDevToolsEnabled)
313 pageDiv.classList.add('apps-dev-tools-mode');
315 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled;
317 ExtensionsList.prototype.data_ = extensionsData;
318 var extensionList = $('extension-settings-list');
319 ExtensionsList.decorate(extensionList);
322 // Indicate that warning |message| has occured for pack of |crx_path| and
323 // |pem_path| files. Ask if user wants override the warning. Send
324 // |overrideFlags| to repeated 'pack' call to accomplish the override.
325 ExtensionSettings.askToOverrideWarning =
326 function(message, crx_path, pem_path, overrideFlags) {
327 var closeAlert = function() {
328 ExtensionSettings.showOverlay(null);
331 alertOverlay.setValues(
332 loadTimeData.getString('packExtensionWarningTitle'),
334 loadTimeData.getString('packExtensionProceedAnyway'),
335 loadTimeData.getString('cancel'),
337 chrome.send('pack', [crx_path, pem_path, overrideFlags]);
341 ExtensionSettings.showOverlay($('alertOverlay'));
345 * Returns the current overlay or null if one does not exist.
346 * @return {Element} The overlay element.
348 ExtensionSettings.getCurrentOverlay = function() {
349 return document.querySelector('#overlay .page.showing');
353 * Sets the given overlay to show. This hides whatever overlay is currently
355 * @param {HTMLElement} node The overlay page to show. If falsey, all overlays
358 ExtensionSettings.showOverlay = function(node) {
359 var pageDiv = $('extension-settings');
361 pageDiv.style.width = window.getComputedStyle(pageDiv).width;
362 document.body.classList.add('no-scroll');
364 document.body.classList.remove('no-scroll');
365 pageDiv.style.width = '';
368 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
369 if (currentlyShowingOverlay)
370 currentlyShowingOverlay.classList.remove('showing');
373 node.classList.add('showing');
375 var pages = document.querySelectorAll('.page');
376 for (var i = 0; i < pages.length; i++) {
377 pages[i].setAttribute('aria-hidden', node ? 'true' : 'false');
380 overlay.hidden = !node;
381 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
382 'stopInterceptingEvents');
386 * Utility function to find the width of various UI strings and synchronize
387 * the width of relevant spans. This is crucial for making sure the
388 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox.
390 function measureCheckboxStrings() {
392 var measuringDiv = $('font-measuring-div');
393 measuringDiv.textContent =
394 loadTimeData.getString('extensionSettingsEnabled');
395 var pxWidth = measuringDiv.clientWidth + trashWidth;
396 measuringDiv.textContent =
397 loadTimeData.getString('extensionSettingsEnable');
398 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth);
399 measuringDiv.textContent =
400 loadTimeData.getString('extensionSettingsDeveloperMode');
401 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth);
403 var style = document.createElement('style');
404 style.type = 'text/css';
406 '.enable-checkbox-text {' +
407 ' min-width: ' + (pxWidth - trashWidth) + 'px;' +
409 '#dev-toggle span {' +
410 ' min-width: ' + pxWidth + 'px;' +
412 document.querySelector('head').appendChild(style);
417 ExtensionSettings: ExtensionSettings
421 window.addEventListener('load', function(e) {
422 extensions.ExtensionSettings.getInstance().initialize();