Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / resources / md_downloads / manager.js
blob563cd1b6daba5c34d43a4ff37ce3f9bbae0e9e52
1 // Copyright 2015 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('downloads', function() {
6   var Manager = Polymer({
7     is: 'downloads-manager',
9     created: function() {
10       /** @private {!downloads.ActionService} */
11       this.actionService_ = new downloads.ActionService;
12     },
14     properties: {
15       hasDownloads_: {
16         type: Boolean,
17         value: false,
18       },
19     },
21     ready: function() {
22       window.addEventListener('resize', this.onResize_.bind(this));
23       // onResize_() doesn't need to be called immediately here because it's
24       // guaranteed to be called again shortly when items are received.
25     },
27     /**
28      * @return {number} A guess at how many items could be visible at once.
29      * @private
30      */
31     guesstimateNumberOfVisibleItems_: function() {
32       var toolbarHeight = this.$.toolbar.offsetHeight;
33       return Math.floor((window.innerHeight - toolbarHeight) / 46) + 1;
34     },
36     /**
37      * @param {Event} e
38      * @private
39      */
40     onCanExecute_: function(e) {
41       e = /** @type {cr.ui.CanExecuteEvent} */(e);
42       switch (e.command.id) {
43         case 'undo-command':
44           e.canExecute = this.$.toolbar.canUndo();
45           break;
46         case 'clear-all-command':
47           e.canExecute = this.$.toolbar.canClearAll();
48           break;
49       }
50     },
52     /**
53      * @param {Event} e
54      * @private
55      */
56     onCommand_: function(e) {
57       if (e.command.id == 'clear-all-command')
58         this.actionService_.clearAll();
59       else if (e.command.id == 'undo-command')
60         this.actionService_.undo();
61     },
63     /** @private */
64     onLoad_: function() {
65       this.$.toolbar.setActionService(this.actionService_);
67       cr.ui.decorate('command', cr.ui.Command);
68       document.addEventListener('canExecute', this.onCanExecute_.bind(this));
69       document.addEventListener('command', this.onCommand_.bind(this));
71       // Shows all downloads.
72       this.actionService_.search('');
73     },
75     /** @private */
76     onResize_: function() {
77       // TODO(dbeam): expose <paper-header-panel>'s #mainContainer in Polymer.
78       var container = this.$.panel.$.mainContainer;
79       var scrollbarWidth = container.offsetWidth - container.clientWidth;
80       this.items_.forEach(function(item) {
81         item.scrollbarWidth = scrollbarWidth;
82       });
83     },
85     /** @private */
86     rebuildFocusGrid_: function() {
87       var activeElement = this.shadowRoot.activeElement;
89       var activeItem;
90       if (activeElement && activeElement.tagName == 'download-item')
91         activeItem = activeElement;
93       var activeControl = activeItem && activeItem.shadowRoot.activeElement;
95       /** @private {!cr.ui.FocusGrid} */
96       this.focusGrid_ = this.focusGrid_ || new cr.ui.FocusGrid;
97       this.focusGrid_.destroy();
99       var boundary = this.$['downloads-list'];
101       this.items_.forEach(function(item) {
102         var focusRow = new downloads.FocusRow(item.content, boundary);
103         this.focusGrid_.addRow(focusRow);
105         if (item == activeItem && !cr.ui.FocusRow.isFocusable(activeControl))
106           focusRow.getEquivalentElement(activeControl).focus();
107       }, this);
109       this.focusGrid_.ensureRowActive();
110     },
112     /**
113      * @return {number} The number of downloads shown on the page.
114      * @private
115      */
116     size_: function() {
117       return this.items_.length;
118     },
120     /**
121      * Called when all items need to be updated.
122      * @param {!Array<!downloads.Data>} list A list of new download data.
123      * @private
124      */
125     updateAll_: function(list) {
126       var oldIdMap = this.idMap_ || {};
128       /** @private {!Object<!downloads.Item>} */
129       this.idMap_ = {};
131       /** @private {!Array<!downloads.Item>} */
132       this.items_ = [];
134       if (!this.iconLoader_) {
135         var guesstimate = Math.max(this.guesstimateNumberOfVisibleItems_(), 1);
136         /** @private {downloads.ThrottledIconLoader} */
137         this.iconLoader_ = new downloads.ThrottledIconLoader(guesstimate);
138       }
140       for (var i = 0; i < list.length; ++i) {
141         var data = list[i];
142         var id = data.id;
144         // Re-use old items when possible (saves work, preserves focus).
145         var item = oldIdMap[id] ||
146             new downloads.Item(this.iconLoader_, this.actionService_);
148         this.idMap_[id] = item;  // Associated by ID for fast lookup.
149         this.items_.push(item);  // Add to sorted list for order.
151         // Render |item| but don't actually add to the DOM yet. |this.items_|
152         // must be fully created to be able to find the right spot to insert.
153         item.update(data);
155         // Collapse redundant dates.
156         var prev = list[i - 1];
157         item.hideDate = !!prev && prev.date_string == data.date_string;
159         delete oldIdMap[id];
160       }
162       // Remove stale, previously rendered items from the DOM.
163       for (var id in oldIdMap) {
164         if (oldIdMap[id].parentNode)
165           oldIdMap[id].parentNode.removeChild(oldIdMap[id]);
166         delete oldIdMap[id];
167       }
169       for (var i = 0; i < this.items_.length; ++i) {
170         var item = this.items_[i];
171         if (item.parentNode)  // Already in the DOM; skip.
172           continue;
174         var before = null;
175         // Find the next rendered item after this one, and insert before it.
176         for (var j = i + 1; !before && j < this.items_.length; ++j) {
177           if (this.items_[j].parentNode)
178             before = this.items_[j];
179         }
180         // If |before| is null, |item| will just get added at the end.
181         this.$['downloads-list'].insertBefore(item, before);
182       }
184       this.hasDownloads_ = this.size_() > 0;
186       if (loadTimeData.getBoolean('allowDeletingHistory'))
187         this.$.toolbar.downloadsShowing = this.hasDownloads_;
189       this.onResize_();
190       this.$.panel.classList.remove('loading');
192       var allReady = this.items_.map(function(i) { return i.readyPromise; });
193       Promise.all(allReady).then(this.rebuildFocusGrid_.bind(this));
194     },
196     /**
197      * @param {!downloads.Data} data
198      * @private
199      */
200     updateItem_: function(data) {
201       var item = this.idMap_[data.id];
203       var activeControl = this.shadowRoot.activeElement == item ?
204           item.shadowRoot.activeElement : null;
206       item.update(data);
208       if (activeControl && !cr.ui.FocusRow.isFocusable(activeControl)) {
209         var focusRow = this.focusGrid_.getRowForRoot(item.content);
210         focusRow.getEquivalentElement(activeControl).focus();
211       }
213       this.onResize_();
214     },
215   });
217   Manager.size = function() {
218     return document.querySelector('downloads-manager').size_();
219   };
221   Manager.updateAll = function(list) {
222     document.querySelector('downloads-manager').updateAll_(list);
223   };
225   Manager.updateItem = function(item) {
226     document.querySelector('downloads-manager').updateItem_(item);
227   };
229   Manager.onLoad = function() {
230     document.querySelector('downloads-manager').onLoad_();
231   };
233   return {Manager: Manager};
236 window.addEventListener('load', downloads.Manager.onLoad);