Home | History | Annotate | Download | only in extensions
      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