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