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. 4 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 11 <if expr="pp_ifdef('chromeos')"> 12 <include src="chromeos/kiosk_apps.js"></include> 13 </if> 14 15 // Used for observing function of the backend datasource for this page by 16 // tests. 17 var webuiResponded = false; 18 19 cr.define('extensions', function() { 20 var ExtensionsList = options.ExtensionsList; 21 22 // Implements the DragWrapper handler interface. 23 var dragWrapperHandler = { 24 /** @override */ 25 shouldAcceptDrag: function(e) { 26 // We can't access filenames during the 'dragenter' event, so we have to 27 // wait until 'drop' to decide whether to do something with the file or 28 // not. 29 // See: http://www.w3.org/TR/2011/WD-html5-20110113/dnd.html#concept-dnd-p 30 return (e.dataTransfer.types && 31 e.dataTransfer.types.indexOf('Files') > -1); 32 }, 33 /** @override */ 34 doDragEnter: function() { 35 chrome.send('startDrag'); 36 ExtensionSettings.showOverlay(null); 37 ExtensionSettings.showOverlay($('dropTargetOverlay')); 38 }, 39 /** @override */ 40 doDragLeave: function() { 41 ExtensionSettings.showOverlay(null); 42 chrome.send('stopDrag'); 43 }, 44 /** @override */ 45 doDragOver: function(e) { 46 e.preventDefault(); 47 }, 48 /** @override */ 49 doDrop: function(e) { 50 ExtensionSettings.showOverlay(null); 51 if (e.dataTransfer.files.length != 1) 52 return; 53 54 var toSend = null; 55 // Files lack a check if they're a directory, but we can find out through 56 // its item entry. 57 for (var i = 0; i < e.dataTransfer.items.length; ++i) { 58 if (e.dataTransfer.items[i].kind == 'file' && 59 e.dataTransfer.items[i].webkitGetAsEntry().isDirectory) { 60 toSend = 'installDroppedDirectory'; 61 break; 62 } 63 } 64 // Only process files that look like extensions. Other files should 65 // navigate the browser normally. 66 if (!toSend && /\.(crx|user\.js)$/i.test(e.dataTransfer.files[0].name)) 67 toSend = 'installDroppedFile'; 68 69 if (toSend) { 70 e.preventDefault(); 71 chrome.send(toSend); 72 } 73 } 74 }; 75 76 /** 77 * ExtensionSettings class 78 * @class 79 */ 80 function ExtensionSettings() {} 81 82 cr.addSingletonGetter(ExtensionSettings); 83 84 ExtensionSettings.prototype = { 85 __proto__: HTMLDivElement.prototype, 86 87 /** 88 * Perform initial setup. 89 */ 90 initialize: function() { 91 uber.onContentFrameLoaded(); 92 cr.ui.FocusOutlineManager.forDocument(document); 93 measureCheckboxStrings(); 94 95 // Set the title. 96 var title = loadTimeData.getString('extensionSettings'); 97 uber.invokeMethodOnParent('setTitle', {title: title}); 98 99 // This will request the data to show on the page and will get a response 100 // back in returnExtensionsData. 101 chrome.send('extensionSettingsRequestExtensionsData'); 102 103 $('toggle-dev-on').addEventListener('change', 104 this.handleToggleDevMode_.bind(this)); 105 $('dev-controls').addEventListener('webkitTransitionEnd', 106 this.handleDevControlsTransitionEnd_.bind(this)); 107 $('open-apps-dev-tools').addEventListener('click', 108 this.handleOpenAppsDevTools_.bind(this)); 109 110 // Set up the three dev mode buttons (load unpacked, pack and update). 111 $('load-unpacked').addEventListener('click', 112 this.handleLoadUnpackedExtension_.bind(this)); 113 $('pack-extension').addEventListener('click', 114 this.handlePackExtension_.bind(this)); 115 $('update-extensions-now').addEventListener('click', 116 this.handleUpdateExtensionNow_.bind(this)); 117 118 if (!loadTimeData.getBoolean('offStoreInstallEnabled')) { 119 this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement, 120 dragWrapperHandler); 121 } 122 123 var packExtensionOverlay = extensions.PackExtensionOverlay.getInstance(); 124 packExtensionOverlay.initializePage(); 125 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)); 130 131 // Initialize the Commands overlay. 132 var extensionCommandsOverlay = 133 extensions.ExtensionCommandsOverlay.getInstance(); 134 extensionCommandsOverlay.initializePage(); 135 136 // Initialize the kiosk overlay. 137 if (cr.isChromeOS) { 138 var kioskOverlay = extensions.KioskAppsOverlay.getInstance(); 139 kioskOverlay.initialize(); 140 141 $('add-kiosk-app').addEventListener('click', function() { 142 ExtensionSettings.showOverlay($('kiosk-apps-page')); 143 kioskOverlay.didShowPage(); 144 }); 145 146 extensions.KioskDisableBailoutConfirm.getInstance().initialize(); 147 } 148 149 cr.ui.overlay.setupOverlay($('dropTargetOverlay')); 150 cr.ui.overlay.globalInitialization(); 151 152 extensions.ExtensionFocusManager.getInstance().initialize(); 153 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_(); 160 } 161 162 preventDefaultOnPoundLinkClicks(); // From webui/js/util.js. 163 }, 164 165 /** 166 * Handles the Load Unpacked Extension button. 167 * @param {Event} e Change event. 168 * @private 169 */ 170 handleLoadUnpackedExtension_: function(e) { 171 chrome.send('extensionSettingsLoadUnpackedExtension'); 172 }, 173 174 /** 175 * Handles the Pack Extension button. 176 * @param {Event} e Change event. 177 * @private 178 */ 179 handlePackExtension_: function(e) { 180 ExtensionSettings.showOverlay($('packExtensionOverlay')); 181 chrome.send('metricsHandler:recordAction', ['Options_PackExtension']); 182 }, 183 184 /** 185 * Shows the Extension Commands configuration UI. 186 * @param {Event} e Change event. 187 * @private 188 */ 189 showExtensionCommandsConfigUi_: function(e) { 190 ExtensionSettings.showOverlay($('extensionCommandsOverlay')); 191 chrome.send('metricsHandler:recordAction', 192 ['Options_ExtensionCommands']); 193 }, 194 195 /** 196 * Handles the Configure (Extension) Commands link. 197 * @param {Event} e Change event. 198 * @private 199 */ 200 handleExtensionCommandsConfig_: function(e) { 201 this.showExtensionCommandsConfigUi_(); 202 }, 203 204 /** 205 * Handles the Update Extension Now button. 206 * @param {Event} e Change event. 207 * @private 208 */ 209 handleUpdateExtensionNow_: function(e) { 210 chrome.send('extensionSettingsAutoupdate'); 211 }, 212 213 /** 214 * Handles the Toggle Dev Mode button. 215 * @param {Event} e Change event. 216 * @private 217 */ 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'); 223 }, 0); 224 } else { 225 $('extension-settings').classList.remove('dev-mode'); 226 } 227 228 chrome.send('extensionSettingsToggleDeveloperMode'); 229 }, 230 231 /** 232 * Called when a transition has ended for #dev-controls. 233 * @param {Event} e webkitTransitionEnd event. 234 * @private 235 */ 236 handleDevControlsTransitionEnd_: function(e) { 237 if (e.propertyName == 'height' && 238 !$('extension-settings').classList.contains('dev-mode')) { 239 $('dev-controls').hidden = true; 240 } 241 }, 242 243 /** 244 * Called when the user clicked on the button to launch Apps Developer 245 * Tools. 246 * @param {!Event} e A click event. 247 * @private 248 */ 249 handleOpenAppsDevTools_: function(e) { 250 chrome.send('extensionSettingsLaunch', 251 ['lphgohfeebnhcpiohjndkgbhhkoapkjc']); 252 }, 253 }; 254 255 /** 256 * Called by the dom_ui_ to re-populate the page with data representing 257 * the current state of installed extensions. 258 */ 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'); 267 }, 0); 268 269 webuiResponded = true; 270 271 if (extensionsData.extensions.length > 0) { 272 // Enforce order specified in the data or (if equal) then sort by 273 // extension name (case-insensitive). 274 extensionsData.extensions.sort(function(a, b) { 275 if (a.order == b.order) { 276 a = a.name.toLowerCase(); 277 b = b.name.toLowerCase(); 278 return a < b ? -1 : (a > b ? 1 : 0); 279 } else { 280 return a.order < b.order ? -1 : 1; 281 } 282 }); 283 } 284 285 var pageDiv = $('extension-settings'); 286 var marginTop = 0; 287 if (extensionsData.profileIsManaged) { 288 pageDiv.classList.add('profile-is-managed'); 289 } else { 290 pageDiv.classList.remove('profile-is-managed'); 291 } 292 if (extensionsData.profileIsManaged) { 293 pageDiv.classList.add('showing-banner'); 294 $('toggle-dev-on').disabled = true; 295 marginTop += 45; 296 } else { 297 pageDiv.classList.remove('showing-banner'); 298 $('toggle-dev-on').disabled = false; 299 } 300 301 pageDiv.style.marginTop = marginTop + 'px'; 302 303 if (extensionsData.developerMode) { 304 pageDiv.classList.add('dev-mode'); 305 $('toggle-dev-on').checked = true; 306 $('dev-controls').hidden = false; 307 } else { 308 pageDiv.classList.remove('dev-mode'); 309 $('toggle-dev-on').checked = false; 310 } 311 312 if (extensionsData.appsDevToolsEnabled) 313 pageDiv.classList.add('apps-dev-tools-mode'); 314 315 $('load-unpacked').disabled = extensionsData.loadUnpackedDisabled; 316 317 ExtensionsList.prototype.data_ = extensionsData; 318 var extensionList = $('extension-settings-list'); 319 ExtensionsList.decorate(extensionList); 320 } 321 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); 329 }; 330 331 alertOverlay.setValues( 332 loadTimeData.getString('packExtensionWarningTitle'), 333 message, 334 loadTimeData.getString('packExtensionProceedAnyway'), 335 loadTimeData.getString('cancel'), 336 function() { 337 chrome.send('pack', [crx_path, pem_path, overrideFlags]); 338 closeAlert(); 339 }, 340 closeAlert); 341 ExtensionSettings.showOverlay($('alertOverlay')); 342 } 343 344 /** 345 * Returns the current overlay or null if one does not exist. 346 * @return {Element} The overlay element. 347 */ 348 ExtensionSettings.getCurrentOverlay = function() { 349 return document.querySelector('#overlay .page.showing'); 350 } 351 352 /** 353 * Sets the given overlay to show. This hides whatever overlay is currently 354 * showing, if any. 355 * @param {HTMLElement} node The overlay page to show. If falsey, all overlays 356 * are hidden. 357 */ 358 ExtensionSettings.showOverlay = function(node) { 359 var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay(); 360 if (currentlyShowingOverlay) 361 currentlyShowingOverlay.classList.remove('showing'); 362 363 if (node) 364 node.classList.add('showing'); 365 366 var pages = document.querySelectorAll('.page'); 367 for (var i = 0; i < pages.length; i++) { 368 pages[i].setAttribute('aria-hidden', node ? 'true' : 'false'); 369 } 370 371 overlay.hidden = !node; 372 uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' : 373 'stopInterceptingEvents'); 374 } 375 376 /** 377 * Utility function to find the width of various UI strings and synchronize 378 * the width of relevant spans. This is crucial for making sure the 379 * Enable/Enabled checkboxes align, as well as the Developer Mode checkbox. 380 */ 381 function measureCheckboxStrings() { 382 var trashWidth = 30; 383 var measuringDiv = $('font-measuring-div'); 384 measuringDiv.textContent = 385 loadTimeData.getString('extensionSettingsEnabled'); 386 var pxWidth = measuringDiv.clientWidth + trashWidth; 387 measuringDiv.textContent = 388 loadTimeData.getString('extensionSettingsEnable'); 389 pxWidth = Math.max(measuringDiv.clientWidth + trashWidth, pxWidth); 390 measuringDiv.textContent = 391 loadTimeData.getString('extensionSettingsDeveloperMode'); 392 pxWidth = Math.max(measuringDiv.clientWidth, pxWidth); 393 394 var style = document.createElement('style'); 395 style.type = 'text/css'; 396 style.textContent = 397 '.enable-checkbox-text {' + 398 ' min-width: ' + (pxWidth - trashWidth) + 'px;' + 399 '}' + 400 '#dev-toggle span {' + 401 ' min-width: ' + pxWidth + 'px;' + 402 '}'; 403 document.querySelector('head').appendChild(style); 404 } 405 406 // Export 407 return { 408 ExtensionSettings: ExtensionSettings 409 }; 410 }); 411 412 var ExtensionSettings = extensions.ExtensionSettings; 413 414 window.addEventListener('load', function(e) { 415 ExtensionSettings.getInstance().initialize(); 416 }); 417