cros: Remove default pinned apps trial.
[chromium-blink-merge.git] / chrome / browser / resources / apps_debugger / js / items_list.js
blob46c7887bb59afaa751d89c2643d84563f69a865b
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.
5 cr.define('apps_dev_tool', function() {
6   'use strict';
8   // The list of all packed/unpacked apps and extensions.
9   var completeList = [];
11   // The list of all packed apps.
12   var packedAppList = [];
14   // The list of all unpacked apps.
15   var unpackedAppList = [];
17   // The list of all packed extensions.
18   var packedExtensionList = [];
20   // The list of all unpacked extensions.
21   var unpackedExtensionList = [];
23   // Highlight animation is in progress in apps or extensions list.
24   var animating = false;
26   // If an update of the list of apps/extensions happened while animation was
27   // in progress, we'll need to update the list at the end of the animation.
28   var needsReloadAppDisplay = false;
30   /** const*/ var AppsDevTool = apps_dev_tool.AppsDevTool;
32   /**
33    * @param {string} a first string.
34    * @param {string} b second string.
35    * @return {number} 1, 0, -1 if |a| is lexicographically greater, equal or
36    *     lesser than |b| respectively.
37    */
38   function compare(a, b) {
39     return a > b ? 1 : (a == b ? 0 : -1);
40   }
42   /**
43    * Returns a translated string.
44    *
45    * Wrapper function to make dealing with translated strings more concise.
46    * Equivalent to localStrings.getString(id).
47    *
48    * @param {string} id The id of the string to return.
49    * @return {string} The translated string.
50    */
51   function str(id) {
52     return loadTimeData.getString(id);
53   }
55   /**
56    * compares strings |app1| and |app2| (case insensitive).
57    * @param {string} app1 first app_name.
58    * @param {string} app2 second app_name.
59    */
60   function compareByName(app1, app2) {
61     return compare(app1.name.toLowerCase(), app2.name.toLowerCase());
62   }
64   /**
65    * Refreshes the app.
66    */
67   function reloadAppDisplay() {
68     var extensions = new ItemsList($('extensions-tab'), packedExtensionList,
69                                    unpackedExtensionList);
70     var apps = new ItemsList($('apps-tab'), packedAppList, unpackedAppList);
71     extensions.showItemNodes();
72     apps.showItemNodes();
73   }
75   /**
76    * Applies the given |filter| to the items list.
77    * @param {string} filter Regular expression to that will be used to
78    *     match the name of the items we want to show.
79    */
80   function rebuildAppList(filter) {
81     packedAppList = [];
82     unpackedAppList = [];
83     packedExtensionList = [];
84     unpackedExtensionList = [];
86     for (var i = 0; i < completeList.length; i++) {
87       var item = completeList[i];
88       if (filter && item.name.toLowerCase().search(filter) < 0)
89         continue;
90       if (item.isApp) {
91         if (item.is_unpacked)
92           unpackedAppList.push(item);
93         else
94           packedAppList.push(item);
95       } else {
96         if (item.is_unpacked)
97           unpackedExtensionList.push(item);
98         else
99           packedExtensionList.push(item);
100       }
101     }
102   }
104   /**
105    * Create item nodes from the metadata.
106    * @constructor
107    */
108   function ItemsList(tabNode, packedItems, unpackedItems) {
109     this.packedItems_ = packedItems;
110     this.unpackedItems_ = unpackedItems;
111     this.tabNode_ = tabNode;
112     assert(this.tabNode_);
113   }
115   ItemsList.prototype = {
117     /**
118      * |packedItems_| holds the metadata of packed apps or extensions.
119      * @type {!Array.<!Object>}
120      * @private
121      */
122     packedItems_: [],
124     /**
125      * |unpackedItems_| holds the metadata of unpacked apps or extensions.
126      * @type {!Array.<!Object>}
127      * @private
128      */
129     unpackedItems_: [],
131     /**
132      * |tabNode_| html element holding the tab containing the list of packed
133      * and unpacked items.
134      * @type {!HTMLElement}
135      * @private
136      */
137     tabNode_: null,
139     /**
140      * Creates all items from scratch.
141      */
142     showItemNodes: function() {
143       // Don't reset the content until the animation is finished.
144       if (animating) {
145         needsReloadAppDisplay = true;
146         return;
147       }
149       var packedItemsList = this.tabNode_.querySelector('.packed-list .items');
150       var unpackedItemsList = this.tabNode_.querySelector(
151           '.unpacked-list .items');
152       packedItemsList.innerHTML = '';
153       unpackedItemsList.innerHTML = '';
155       // Iterate over the items and add each item to the list.
156       this.tabNode_.classList.toggle('empty-item-list',
157                                      this.packedItems_.length == 0 &&
158                                      this.unpackedItems_.length == 0);
160       // Iterate over the items in the packed items and add each item to the
161       // list.
162       this.tabNode_.querySelector('.packed-list').classList.toggle(
163           'empty-item-list', this.packedItems_.length == 0);
164       for (var i = 0; i < this.packedItems_.length; ++i) {
165         packedItemsList.appendChild(this.createNode_(this.packedItems_[i]));
166       }
168       // Iterate over the items in the unpacked items and add each item to the
169       // list.
170       this.tabNode_.querySelector('.unpacked-list').classList.toggle(
171           'empty-item-list', this.unpackedItems_.length == 0);
172       for (var i = 0; i < this.unpackedItems_.length; ++i) {
173         unpackedItemsList.appendChild(this.createNode_(this.unpackedItems_[i]));
174       }
175     },
177     /**
178      * Synthesizes and initializes an HTML element for the item metadata
179      * given in |item|.
180      * @param {!Object} item A dictionary of item metadata.
181      * @return {!Node} The newly created node.
182      * @private
183      */
184     createNode_: function(item) {
185       var template = $('template-collection').querySelector(
186           '.extension-list-item-wrapper');
187       var node = template.cloneNode(true);
188       node.id = item.id;
190       if (!item.enabled)
191         node.classList.add('inactive-extension');
193       node.querySelector('.extension-disabled').hidden = item.enabled;
195       if (!item.may_disable)
196         node.classList.add('may-not-disable');
198       var itemNode = node.querySelector('.extension-list-item');
199       itemNode.style.backgroundImage = 'url(' + item.icon_url + ')';
201       var title = node.querySelector('.extension-title');
202       title.textContent = item.name;
203       title.onclick = function() {
204         if (item.isApp)
205           ItemsList.launchApp(item.id);
206       };
208       var version = node.querySelector('.extension-version');
209       version.textContent = item.version;
211       var description = node.querySelector('.extension-description span');
212       description.textContent = item.description;
214       // The 'allow in incognito' checkbox.
215       this.setAllowIncognitoCheckbox_(item, node);
217       // The 'allow file:// access' checkbox.
218       if (item.wants_file_access)
219         this.setAllowFileAccessCheckbox_(item, node);
221       // The 'Options' checkbox.
222       if (item.enabled && item.options_url) {
223         var options = node.querySelector('.options-link');
224         options.href = item.options_url;
225         options.hidden = false;
226       }
228       // The 'Permissions' link.
229       this.setPermissionsLink_(item, node);
231       // The 'View in Web Store/View Web Site' link.
232       if (item.homepage_url)
233         this.setWebstoreLink_(item, node);
235       // The 'Reload' checkbox.
236       if (item.allow_reload)
237         this.setReloadLink_(item, node);
239       if (item.type == 'packaged_app') {
240         // The 'Launch' link.
241         var launch = node.querySelector('.launch-link');
242         launch.addEventListener('click', function(e) {
243           ItemsList.launchApp(item.id);
244         });
245         launch.hidden = false;
246       }
247       // The terminated reload link.
248       if (!item.terminated)
249         this.setEnabledCheckbox_(item, node);
250       else
251         this.setTerminatedReloadLink_(item, node);
253       // Set delete button handler.
254       this.setDeleteButton_(item, node);
256       // First get the item id.
257       var idLabel = node.querySelector('.extension-id');
258       idLabel.textContent = ' ' + item.id;
260       // Set the path and show the pack button, if provided by unpacked
261       // app / extension.
262       if (item.is_unpacked) {
263         var loadPath = node.querySelector('.load-path');
264         loadPath.hidden = false;
265         loadPath.querySelector('span:nth-of-type(2)').textContent =
266             ' ' + item.path;
267         this.setPackButton_(item, node);
268       }
270       // Then the 'managed, cannot uninstall/disable' message.
271       if (!item.may_disable)
272         node.querySelector('.managed-message').hidden = false;
274       // The install warnings.
275       if (item.install_warnings.length > 0) {
276         var panel = node.querySelector('.install-warnings');
277         panel.hidden = false;
278         var list = panel.querySelector('ul');
279         item.install_warnings.forEach(function(warning) {
280           var li = document.createElement('li');
281           li.textContent = warning.message;
282           list.appendChild(li);
283         });
284       }
286       this.setActiveViews_(item, node);
288       return node;
289     },
291     /**
292      * Sets the webstore link.
293      * @param {!Object} item A dictionary of item metadata.
294      * @param {!HTMLElement} el HTML element containing all items.
295      * @private
296      */
297     setWebstoreLink_: function(item, el) {
298       var siteLink = el.querySelector('.site-link');
299       siteLink.href = item.homepage_url;
300       siteLink.textContent = str(item.homepageProvided ?
301          'extensionSettingsVisitWebsite' : 'extensionSettingsVisitWebStore');
302       siteLink.hidden = false;
303       siteLink.target = '_blank';
304     },
306     /**
307      * Sets the reload link handler.
308      * @param {!Object} item A dictionary of item metadata.
309      * @param {!HTMLElement} el HTML element containing all items.
310      * @private
311      */
312     setReloadLink_: function(item, el) {
313       var reload = el.querySelector('.reload-link');
314       reload.addEventListener('click', function(e) {
315         chrome.developerPrivate.reload(item.id, function() {
316           ItemsList.loadItemsInfo();
317         });
318       });
319       reload.hidden = false;
320     },
322     /**
323      * Sets the terminated reload link handler.
324      * @param {!Object} item A dictionary of item metadata.
325      * @param {!HTMLElement} el HTML element containing all items.
326      * @private
327      */
328     setTerminatedReloadLink_: function(item, el) {
329       var terminatedReload = el.querySelector('.terminated-reload-link');
330       terminatedReload.addEventListener('click', function(e) {
331         chrome.developerPrivate.reload(item.id, function() {
332           ItemsList.loadItemsInfo();
333         });
334       });
335       terminatedReload.hidden = false;
336     },
338     /**
339      * Sets the permissions link handler.
340      * @param {!Object} item A dictionary of item metadata.
341      * @param {!HTMLElement} el HTML element containing all items.
342      * @private
343      */
344     setPermissionsLink_: function(item, el) {
345       var permissions = el.querySelector('.permissions-link');
346       permissions.addEventListener('click', function(e) {
347         chrome.developerPrivate.showPermissionsDialog(item.id);
348       });
349     },
351     /**
352      * Sets the pack button handler.
353      * @param {!Object} item A dictionary of item metadata.
354      * @param {!HTMLElement} el HTML element containing all items.
355      * @private
356      */
357     setPackButton_: function(item, el) {
358       var packButton = el.querySelector('.pack-link');
359       packButton.addEventListener('click', function(e) {
360         if (item.isApp) {
361           $('pack-heading').textContent =
362               loadTimeData.getString('packAppHeading');
363           $('pack-title').textContent =
364               loadTimeData.getString('packAppOverlay');
365         } else {
366           $('pack-heading').textContent =
367               loadTimeData.getString('packExtensionHeading');
368           $('pack-title').textContent =
369               loadTimeData.getString('packExtensionOverlay');
370         }
371         $('item-root-dir').value = item.path;
372         AppsDevTool.showOverlay($('packItemOverlay'));
373       });
374       packButton.hidden = false;
375     },
377     /**
378      * Shows the delete confirmation dialog and will trigger the deletion
379      * if the user confirms deletion.
380      * @param {!Object} item Information about the item being deleted.
381      * @private
382      */
383     showDeleteConfirmationDialog: function(item) {
384       var message;
385       if (item.isApp)
386         message = loadTimeData.getString('deleteConfirmationMessageApp');
387       else
388         message = loadTimeData.getString('deleteConfirmationMessageExtension');
390       alertOverlay.setValues(
391           loadTimeData.getString('deleteConfirmationTitle'),
392           message,
393           loadTimeData.getString('deleteConfirmationDeleteButton'),
394           loadTimeData.getString('cancel'),
395           function() {
396             AppsDevTool.showOverlay(null);
397             var options = {showConfirmDialog: false};
398             chrome.management.uninstall(item.id, options);
399           },
400           function() {
401             AppsDevTool.showOverlay(null);
402           });
404       AppsDevTool.showOverlay($('alertOverlay'));
405     },
407     /**
408      * Sets the delete button handler.
409      * @param {!Object} item A dictionary of item metadata.
410      * @param {!HTMLElement} el HTML element containing all items.
411      * @private
412      */
413     setDeleteButton_: function(item, el) {
414       var deleteLink = el.querySelector('.delete-link');
415       deleteLink.addEventListener('click', function(e) {
416         this.showDeleteConfirmationDialog(item);
417       }.bind(this));
418     },
420     /**
421      * Sets the handler for enable checkbox.
422      * @param {!Object} item A dictionary of item metadata.
423      * @param {!HTMLElement} el HTML element containing all items.
424      * @private
425      */
426     setEnabledCheckbox_: function(item, el) {
427       var enable = el.querySelector('.enable-checkbox');
428       enable.hidden = false;
429       enable.querySelector('input').disabled = !item.may_disable;
431       if (item.may_disable) {
432         enable.addEventListener('click', function(e) {
433           chrome.developerPrivate.enable(
434               item.id, !!e.target.checked, function() {
435                 ItemsList.loadItemsInfo();
436               });
437         });
438       }
440       enable.querySelector('input').checked = item.enabled;
441     },
443     /**
444      * Sets the handler for the allow_file_access checkbox.
445      * @param {!Object} item A dictionary of item metadata.
446      * @param {!HTMLElement} el HTML element containing all items.
447      * @private
448      */
449     setAllowFileAccessCheckbox_: function(item, el) {
450       var fileAccess = el.querySelector('.file-access-control');
451       fileAccess.addEventListener('click', function(e) {
452         chrome.developerPrivate.allowFileAccess(item.id, !!e.target.checked);
453       });
454       fileAccess.querySelector('input').checked = item.allow_file_access;
455       fileAccess.hidden = false;
456     },
458     /**
459      * Sets the handler for the allow_incognito checkbox.
460      * @param {!Object} item A dictionary of item metadata.
461      * @param {!HTMLElement} el HTML element containing all items.
462      * @private
463      */
464     setAllowIncognitoCheckbox_: function(item, el) {
465       if (item.allow_incognito) {
466         var incognito = el.querySelector('.incognito-control');
467         incognito.addEventListener('change', function(e) {
468           chrome.developerPrivate.allowIncognito(
469               item.id, !!e.target.checked, function() {
470             ItemsList.loadItemsInfo();
471           });
472         });
473         incognito.querySelector('input').checked = item.incognito_enabled;
474         incognito.hidden = false;
475       }
476     },
478     /**
479      * Sets the active views link of an item. Clicking on the link
480      * opens devtools window to inspect.
481      * @param {!Object} item A dictionary of item metadata.
482      * @param {!HTMLElement} el HTML element containing all items.
483      * @private
484      */
485     setActiveViews_: function(item, el) {
486       if (!item.views.length)
487         return;
489       var activeViews = el.querySelector('.active-views');
490       activeViews.hidden = false;
491       var link = activeViews.querySelector('a');
493       item.views.forEach(function(view, i) {
494         var displayName = view.generatedBackgroundPage ?
495             str('backgroundPage') : view.path;
496         var label =
497             displayName + (view.incognito ? ' ' + str('viewIncognito') : '') +
498             (view.render_process_id == -1 ? ' ' + str('viewInactive') : '');
499         link.textContent = label;
500         link.addEventListener('click', function(e) {
501           // Opens the devtools inspect window for the page.
502           chrome.developerPrivate.inspect({
503             extension_id: String(item.id),
504             render_process_id: String(view.render_process_id),
505             render_view_id: String(view.render_view_id),
506             incognito: view.incognito,
507           });
508         });
510         if (i < item.views.length - 1) {
511           link = link.cloneNode(true);
512           activeViews.appendChild(link);
513         }
514       });
515     },
516   };
518   /**
519    * Rebuilds the item list and reloads the app on every search input.
520    */
521   ItemsList.onSearchInput = function() {
522     // Escape regexp special chars (e.g. ^, $, etc.).
523     rebuildAppList($('search').value.toLowerCase().replace(
524         /[-\/\\^$*+?.()|[\]{}]/g, '\\$&'));
525     reloadAppDisplay();
526   };
528   /**
529    * Fetches items info and reloads the app.
530    * @param {Function=} opt_callback An optional callback to be run when
531    *     reloading is finished.
532    */
533   ItemsList.loadItemsInfo = function(callback) {
534     chrome.developerPrivate.getItemsInfo(true, true, function(info) {
535       completeList = info.sort(compareByName);
536       ItemsList.onSearchInput();
537       assert(/undefined|function/.test(typeof callback));
538       if (callback)
539         callback();
540     });
541   };
543   /**
544    * Launches the item with id |id|.
545    * @param {string} id Item ID.
546    */
547   ItemsList.launchApp = function(id) {
548     chrome.management.launchApp(id, function() {
549       ItemsList.loadItemsInfo();
550     });
551   };
553   /**
554    * Selects the unpacked apps / extensions tab, scrolls to the app /extension
555    * with the given |id| and expand its details.
556    * @param {string} id Identifier of the app / extension.
557    */
558   ItemsList.makeUnpackedExtensionVisible = function(id) {
559     // Find which tab contains the item.
560     var tabbox = document.querySelector('tabbox');
562     // Select the correct tab.
563     var node = $(id);
564     if (!node)
565       return;
567     var tabNode = findAncestor(node, function(el) {
568       return el.tagName == 'TABPANEL';
569     });
570     tabbox.selectedIndex = tabNode == $('apps-tab') ? 0 : 1;
572     // Highlights the item.
573     animating = true;
574     needsReloadAppDisplay = true;
575     node.classList.add('highlighted');
576     // Show highlighted item for 1 sec.
577     setTimeout(function() {
578       node.style.backgroundColor = 'rgba(255, 255, 128, 0)';
579       // Wait for fade animation to happen.
580       node.addEventListener('webkitTransitionEnd', function f(e) {
581         assert(e.propertyName == 'background-color');
583         animating = false;
584         if (needsReloadAppDisplay)
585           reloadAppDisplay();
587         node.removeEventListener('webkitTransitionEnd', f);
588       });
589     }, 1000);
591     // Scroll relatively to the position of the first item.
592     var header = tabNode.querySelector('.unpacked-list .list-header');
593     if (node.offsetTop - header.offsetTop < tabNode.scrollTop) {
594       // Some padding between the top edge and the node is already provided
595       // by the HTML layout.
596       tabNode.scrollTop = node.offsetTop - header.offsetTop;
597     } else if (node.offsetTop + node.offsetHeight > tabNode.scrollTop +
598         tabNode.offsetHeight + 20) {
599       // Adds padding of 20px between the bottom edge and the bottom of the
600       // node.
601       tabNode.scrollTop = node.offsetTop + node.offsetHeight -
602           tabNode.offsetHeight + 20;
603     }
604   };
606   return {
607     ItemsList: ItemsList,
608   };