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