- add sources.
[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"></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>
11
12 <if expr="pp_ifdef('chromeos')">
13 <include src="chromeos/kiosk_apps.js"></include>
14 </if>
15
16 // Used for observing function of the backend datasource for this page by
17 // tests.
18 var webuiResponded = false;
19
20 cr.define('extensions', function() {
21   var ExtensionsList = options.ExtensionsList;
22
23   // Implements the DragWrapper handler interface.
24   var dragWrapperHandler = {
25     /** @override */
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
29       // not.
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);
33     },
34     /** @override */
35     doDragEnter: function() {
36       chrome.send('startDrag');
37       ExtensionSettings.showOverlay(null);
38       ExtensionSettings.showOverlay($('drop-target-overlay'));
39     },
40     /** @override */
41     doDragLeave: function() {
42       ExtensionSettings.showOverlay(null);
43       chrome.send('stopDrag');
44     },
45     /** @override */
46     doDragOver: function(e) {
47       e.preventDefault();
48     },
49     /** @override */
50     doDrop: function(e) {
51       ExtensionSettings.showOverlay(null);
52       if (e.dataTransfer.files.length != 1)
53         return;
54
55       var toSend = null;
56       // Files lack a check if they're a directory, but we can find out through
57       // its item entry.
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';
62           break;
63         }
64       }
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';
69
70       if (toSend) {
71         e.preventDefault();
72         chrome.send(toSend);
73       }
74     }
75   };
76
77   /**
78    * ExtensionSettings class
79    * @class
80    */
81   function ExtensionSettings() {}
82
83   cr.addSingletonGetter(ExtensionSettings);
84
85   ExtensionSettings.prototype = {
86     __proto__: HTMLDivElement.prototype,
87
88     /**
89      * Perform initial setup.
90      */
91     initialize: function() {
92       uber.onContentFrameLoaded();
93       cr.ui.FocusOutlineManager.forDocument(document);
94       measureCheckboxStrings();
95
96       // Set the title.
97       var title = loadTimeData.getString('extensionSettings');
98       uber.invokeMethodOnParent('setTitle', {title: title});
99
100       // This will request the data to show on the page and will get a response
101       // back in returnExtensionsData.
102       chrome.send('extensionSettingsRequestExtensionsData');
103
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));
110
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));
118
119       if (!loadTimeData.getBoolean('offStoreInstallEnabled')) {
120         this.dragWrapper_ = new cr.ui.DragWrapper(document.documentElement,
121                                                   dragWrapperHandler);
122       }
123
124       extensions.PackExtensionOverlay.getInstance().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       extensions.ExtensionCommandsOverlay.getInstance().initializePage();
133
134       extensions.ExtensionErrorOverlay.getInstance().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($('drop-target-overlay'));
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($('pack-extension-overlay'));
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($('extension-commands-overlay'));
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) 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);
278         }
279         return compare(a.order, b.order) ||
280                compare(a.name.toLowerCase(), b.name.toLowerCase()) ||
281                compare(a.id, b.id);
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 pageDiv = $('extension-settings');
360     if (node) {
361       pageDiv.style.width = window.getComputedStyle(pageDiv).width;
362       document.body.classList.add('no-scroll');
363     } else {
364       document.body.classList.remove('no-scroll');
365       pageDiv.style.width = '';
366     }
367
368     var currentlyShowingOverlay = ExtensionSettings.getCurrentOverlay();
369     if (currentlyShowingOverlay)
370       currentlyShowingOverlay.classList.remove('showing');
371
372     if (node)
373       node.classList.add('showing');
374
375     var pages = document.querySelectorAll('.page');
376     for (var i = 0; i < pages.length; i++) {
377       pages[i].setAttribute('aria-hidden', node ? 'true' : 'false');
378     }
379
380     overlay.hidden = !node;
381     uber.invokeMethodOnParent(node ? 'beginInterceptingEvents' :
382                                      'stopInterceptingEvents');
383   }
384
385   /**
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.
389    */
390   function measureCheckboxStrings() {
391     var trashWidth = 30;
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);
402
403     var style = document.createElement('style');
404     style.type = 'text/css';
405     style.textContent =
406         '.enable-checkbox-text {' +
407         '  min-width: ' + (pxWidth - trashWidth) + 'px;' +
408         '}' +
409         '#dev-toggle span {' +
410         '  min-width: ' + pxWidth + 'px;' +
411         '}';
412     document.querySelector('head').appendChild(style);
413   }
414
415   // Export
416   return {
417     ExtensionSettings: ExtensionSettings
418   };
419 });
420
421 window.addEventListener('load', function(e) {
422   extensions.ExtensionSettings.getInstance().initialize();
423 });