Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(78)

Side by Side Diff: chrome/browser/resources/downloads/downloads.js

Issue 977473002: downloads: break downloads.js into more classes/files. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: thestig@ review Created 5 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 // TODO(hcarmona): This file is big: it may be good to split it up.
6
7 /**
8 * The type of the download object. The definition is based on
9 * chrome/browser/ui/webui/downloads_dom_handler.cc:CreateDownloadItemValue()
10 * @typedef {{by_ext_id: (string|undefined),
11 * by_ext_name: (string|undefined),
12 * danger_type: (string|undefined),
13 * date_string: string,
14 * file_externally_removed: boolean,
15 * file_name: string,
16 * file_path: string,
17 * file_url: string,
18 * id: string,
19 * last_reason_text: (string|undefined),
20 * otr: boolean,
21 * percent: (number|undefined),
22 * progress_status_text: (string|undefined),
23 * received: (number|undefined),
24 * resume: boolean,
25 * retry: boolean,
26 * since_string: string,
27 * started: number,
28 * state: string,
29 * total: number,
30 * url: string}}
31 */
32 var DownloadItem;
33
34 /**
35 * Creates a link with a specified onclick handler and content.
36 * @param {function()} onclick The onclick handler.
37 * @param {string=} opt_text The link text.
38 * @return {!Element} The created link element.
39 */
40 function createActionLink(onclick, opt_text) {
41 var link = new ActionLink;
42 link.onclick = onclick;
43 if (opt_text) link.textContent = opt_text;
44 return link;
45 }
46
47 /**
48 * Creates a button with a specified onclick handler and content.
49 * @param {function()} onclick The onclick handler.
50 * @param {string} value The button text.
51 * @return {Element} The created button.
52 */
53 function createButton(onclick, value) {
54 var button = document.createElement('input');
55 button.type = 'button';
56 button.value = value;
57 button.onclick = onclick;
58 return button;
59 }
60
61 ///////////////////////////////////////////////////////////////////////////////
62 // DownloadFocusRow:
63
64 /**
65 * Provides an implementation for a single column grid.
66 * @constructor
67 * @extends {cr.ui.FocusRow}
68 */
69 function DownloadFocusRow() {}
70
71 /**
72 * Decorates |focusRow| so that it can be treated as a DownloadFocusRow.
73 * @param {Element} focusRow The element that has all the columns represented
74 * by |download|.
75 * @param {Download} download The Download representing this row.
76 * @param {Node} boundary Focus events are ignored outside of this node.
77 */
78 DownloadFocusRow.decorate = function(focusRow, download, boundary) {
79 focusRow.__proto__ = DownloadFocusRow.prototype;
80 focusRow.decorate(boundary);
81
82 // Add all clickable elements as a row into the grid.
83 focusRow.addElementIfFocusable_(download.nodeFileLink_, 'name');
84 focusRow.addElementIfFocusable_(download.nodeURL_, 'url');
85 focusRow.addElementIfFocusable_(download.controlShow_, 'show');
86 focusRow.addElementIfFocusable_(download.controlRetry_, 'retry');
87 focusRow.addElementIfFocusable_(download.controlPause_, 'pause');
88 focusRow.addElementIfFocusable_(download.controlResume_, 'resume');
89 focusRow.addElementIfFocusable_(download.controlRemove_, 'remove');
90 focusRow.addElementIfFocusable_(download.controlCancel_, 'cancel');
91 focusRow.addElementIfFocusable_(download.malwareSave_, 'save');
92 focusRow.addElementIfFocusable_(download.dangerSave_, 'save');
93 focusRow.addElementIfFocusable_(download.malwareDiscard_, 'discard');
94 focusRow.addElementIfFocusable_(download.dangerDiscard_, 'discard');
95 focusRow.addElementIfFocusable_(download.controlByExtensionLink_,
96 'extension');
97 };
98
99 DownloadFocusRow.prototype = {
100 __proto__: cr.ui.FocusRow.prototype,
101
102 /** @override */
103 getEquivalentElement: function(element) {
104 if (this.focusableElements.indexOf(element) > -1)
105 return element;
106
107 // All elements default to another element with the same type.
108 var columnType = element.getAttribute('column-type');
109 var equivalent = this.querySelector('[column-type=' + columnType + ']');
110
111 if (this.focusableElements.indexOf(equivalent) < 0) {
112 var equivalentTypes =
113 ['show', 'retry', 'pause', 'resume', 'remove', 'cancel'];
114 if (equivalentTypes.indexOf(columnType) != -1) {
115 var allTypes = equivalentTypes.map(function(type) {
116 return '[column-type=' + type + ']:not([hidden])';
117 }).join(', ');
118 equivalent = this.querySelector(allTypes);
119 }
120 }
121
122 // Return the first focusable element if no equivalent element is found.
123 return equivalent || this.focusableElements[0];
124 },
125
126 /**
127 * @param {Element} element The element that should be added.
128 * @param {string} type The column type to use for the element.
129 * @private
130 */
131 addElementIfFocusable_: function(element, type) {
132 if (this.shouldFocus_(element)) {
133 this.addFocusableElement(element);
134 element.setAttribute('column-type', type);
135 }
136 },
137
138 /**
139 * Determines if element should be focusable.
140 * @param {Element} element
141 * @return {boolean}
142 * @private
143 */
144 shouldFocus_: function(element) {
145 if (!element)
146 return false;
147
148 // Hidden elements are not focusable.
149 var style = window.getComputedStyle(element);
150 if (style.visibility == 'hidden' || style.display == 'none')
151 return false;
152
153 // Verify all ancestors are focusable.
154 return !element.parentElement || this.shouldFocus_(element.parentElement);
155 },
156 };
157
158 ///////////////////////////////////////////////////////////////////////////////
159 // Downloads
160 /**
161 * Class to hold all the information about the visible downloads.
162 * @constructor
163 */
164 function Downloads() {
165 /**
166 * @type {!Object<string, Download>}
167 * @private
168 */
169 this.downloads_ = {};
170 this.node_ = $('downloads-display');
171 this.summary_ = $('downloads-summary-text');
172 this.searchText_ = '';
173 this.focusGrid_ = new cr.ui.FocusGrid();
174
175 // Keep track of the dates of the newest and oldest downloads so that we
176 // know where to insert them.
177 this.newestTime_ = -1;
178
179 // Icon load request queue.
180 this.iconLoadQueue_ = [];
181 this.isIconLoading_ = false;
182
183 this.progressForeground1_ = new Image();
184 this.progressForeground1_.src =
185 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32@1x';
186 this.progressForeground2_ = new Image();
187 this.progressForeground2_.src =
188 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32@2x';
189
190 cr.ui.decorate('command', cr.ui.Command);
191 document.addEventListener('canExecute', this.onCanExecute_.bind(this));
192 document.addEventListener('command', this.onCommand_.bind(this));
193 }
194
195 /**
196 * Called when a download has been updated or added.
197 * @param {DownloadItem} download Information about a download.
198 */
199 Downloads.prototype.updated = function(download) {
200 var id = download.id;
201 if (this.downloads_[id]) {
202 this.downloads_[id].update(download);
203 } else {
204 this.downloads_[id] = new Download(download);
205 // We get downloads in display order, so we don't have to worry about
206 // maintaining correct order - we can assume that any downloads not in
207 // display order are new ones and so we can add them to the top of the
208 // list.
209 if (download.started > this.newestTime_) {
210 this.node_.insertBefore(this.downloads_[id].node, this.node_.firstChild);
211 this.newestTime_ = download.started;
212 } else {
213 this.node_.appendChild(this.downloads_[id].node);
214 }
215 }
216 // Download.prototype.update may change its nodeSince_ and nodeDate_, so
217 // update all the date displays.
218 // TODO(benjhayden) Only do this if its nodeSince_ or nodeDate_ actually did
219 // change since this may touch 150 elements and Downloads.prototype.updated
220 // may be called 150 times.
221 this.onDownloadListChanged_();
222 };
223
224 /**
225 * Set our display search text.
226 * @param {string} searchText The string we're searching for.
227 */
228 Downloads.prototype.setSearchText = function(searchText) {
229 this.searchText_ = searchText;
230 };
231
232 /** Update the summary block above the results. */
233 Downloads.prototype.updateSummary = function() {
234 if (this.searchText_) {
235 this.summary_.textContent = loadTimeData.getStringF('searchresultsfor',
236 this.searchText_);
237 } else {
238 this.summary_.textContent = '';
239 }
240 };
241
242 /**
243 * Called when either a search or load completes to update whether there are
244 * results or not.
245 */
246 Downloads.prototype.updateResults = function() {
247 var noDownloadsOrResults = $('no-downloads-or-results');
248 noDownloadsOrResults.textContent = loadTimeData.getString(
249 this.searchText_ ? 'no_search_results' : 'no_downloads');
250
251 var hasDownloads = this.size() > 0;
252 this.node_.hidden = !hasDownloads;
253 noDownloadsOrResults.hidden = hasDownloads;
254
255 if (loadTimeData.getBoolean('allow_deleting_history'))
256 $('clear-all').hidden = !hasDownloads || this.searchText_.length > 0;
257
258 this.rebuildFocusGrid_();
259 };
260
261 /**
262 * Rebuild the focusGrid_ using the elements that each download will have.
263 * @private
264 */
265 Downloads.prototype.rebuildFocusGrid_ = function() {
266 var activeElement = document.activeElement;
267 this.focusGrid_.destroy();
268
269 var keys = Object.keys(this.downloads_);
270 for (var i = 0; i < keys.length; ++i) {
271 var download = this.downloads_[keys[i]];
272 DownloadFocusRow.decorate(download.node, download, this.node_);
273 }
274
275 // The ordering of the keys is not guaranteed, and downloads should be added
276 // to the FocusGrid in the order they will be in the UI.
277 var downloads = document.querySelectorAll('.download');
278 for (var i = 0; i < downloads.length; ++i) {
279 var focusRow = downloads[i];
280 this.focusGrid_.addRow(focusRow);
281
282 // Focus the equivalent element in the focusRow because the active element
283 // may no longer be visible.
284 if (focusRow.contains(activeElement))
285 focusRow.getEquivalentElement(activeElement).focus();
286 }
287 };
288
289 /**
290 * Returns the number of downloads in the model. Used by tests.
291 * @return {number} Returns the number of downloads shown on the page.
292 */
293 Downloads.prototype.size = function() {
294 return Object.keys(this.downloads_).length;
295 };
296
297 /**
298 * Called whenever the downloads lists items have changed (either by being
299 * updated, added, or removed).
300 * @private
301 */
302 Downloads.prototype.onDownloadListChanged_ = function() {
303 // Update the date visibility in our nodes so that no date is repeated.
304 var dateContainers = document.getElementsByClassName('date-container');
305 var displayed = {};
306 for (var i = 0, container; container = dateContainers[i]; i++) {
307 var dateString = container.getElementsByClassName('date')[0].innerHTML;
308 if (displayed[dateString]) {
309 container.style.display = 'none';
310 } else {
311 displayed[dateString] = true;
312 container.style.display = 'block';
313 }
314 }
315
316 this.updateResults();
317 };
318
319 /**
320 * Remove a download.
321 * @param {string} id The id of the download to remove.
322 */
323 Downloads.prototype.remove = function(id) {
324 this.node_.removeChild(this.downloads_[id].node);
325 delete this.downloads_[id];
326 this.onDownloadListChanged_();
327 };
328
329 /** Clear all downloads and reset us back to a null state. */
330 Downloads.prototype.clear = function() {
331 for (var id in this.downloads_) {
332 this.downloads_[id].clear();
333 this.remove(id);
334 }
335 };
336
337 /**
338 * Schedule icon load.
339 * @param {HTMLImageElement} elem Image element that should contain the icon.
340 * @param {string} iconURL URL to the icon.
341 */
342 Downloads.prototype.scheduleIconLoad = function(elem, iconURL) {
343 var self = this;
344
345 // Sends request to the next icon in the queue and schedules
346 // call to itself when the icon is loaded.
347 function loadNext() {
348 self.isIconLoading_ = true;
349 while (self.iconLoadQueue_.length > 0) {
350 var request = self.iconLoadQueue_.shift();
351 var oldSrc = request.element.src;
352 request.element.onabort = request.element.onerror =
353 request.element.onload = loadNext;
354 request.element.src = request.url;
355 if (oldSrc != request.element.src)
356 return;
357 }
358 self.isIconLoading_ = false;
359 }
360
361 // Create new request
362 var loadRequest = {element: elem, url: iconURL};
363 this.iconLoadQueue_.push(loadRequest);
364
365 // Start loading if none scheduled yet
366 if (!this.isIconLoading_)
367 loadNext();
368 };
369
370 /**
371 * Returns whether the displayed list needs to be updated or not.
372 * @param {Array} downloads Array of download nodes.
373 * @return {boolean} Returns true if the displayed list is to be updated.
374 */
375 Downloads.prototype.isUpdateNeeded = function(downloads) {
376 var size = 0;
377 for (var i in this.downloads_)
378 size++;
379 if (size != downloads.length)
380 return true;
381 // Since there are the same number of items in the incoming list as
382 // |this.downloads_|, there won't be any removed downloads without some
383 // downloads having been inserted. So check only for new downloads in
384 // deciding whether to update.
385 for (var i = 0; i < downloads.length; i++) {
386 if (!this.downloads_[downloads[i].id])
387 return true;
388 }
389 return false;
390 };
391
392 /**
393 * @param {Event} e
394 * @private
395 */
396 Downloads.prototype.onCanExecute_ = function(e) {
397 e = /** @type {cr.ui.CanExecuteEvent} */(e);
398 e.canExecute = document.activeElement != $('term');
399 };
400
401 /**
402 * @param {Event} e
403 * @private
404 */
405 Downloads.prototype.onCommand_ = function(e) {
406 if (e.command.id == 'undo-command')
407 chrome.send('undo');
408 else if (e.command.id == 'clear-all-command')
409 clearAll();
410 };
411
412 ///////////////////////////////////////////////////////////////////////////////
413 // Download
414 /**
415 * A download and the DOM representation for that download.
416 * @param {DownloadItem} download Info about the download.
417 * @constructor
418 */
419 function Download(download) {
420 // Create DOM
421 this.node = createElementWithClassName(
422 'div', 'download' + (download.otr ? ' otr' : ''));
423
424 // Dates
425 this.dateContainer_ = createElementWithClassName('div', 'date-container');
426 this.node.appendChild(this.dateContainer_);
427
428 this.nodeSince_ = createElementWithClassName('div', 'since');
429 this.nodeDate_ = createElementWithClassName('div', 'date');
430 this.dateContainer_.appendChild(this.nodeSince_);
431 this.dateContainer_.appendChild(this.nodeDate_);
432
433 // Container for all 'safe download' UI.
434 this.safe_ = createElementWithClassName('div', 'safe');
435 this.safe_.ondragstart = this.drag_.bind(this);
436 this.node.appendChild(this.safe_);
437
438 if (download.state != Download.States.COMPLETE) {
439 this.nodeProgressBackground_ =
440 createElementWithClassName('div', 'progress background');
441 this.safe_.appendChild(this.nodeProgressBackground_);
442
443 this.nodeProgressForeground_ =
444 createElementWithClassName('canvas', 'progress');
445 this.nodeProgressForeground_.width = Download.Progress.width;
446 this.nodeProgressForeground_.height = Download.Progress.height;
447 this.canvasProgress_ = this.nodeProgressForeground_.getContext('2d');
448
449 this.safe_.appendChild(this.nodeProgressForeground_);
450 }
451
452 this.nodeImg_ = createElementWithClassName('img', 'icon');
453 this.nodeImg_.alt = '';
454 this.safe_.appendChild(this.nodeImg_);
455
456 // FileLink is used for completed downloads, otherwise we show FileName.
457 this.nodeTitleArea_ = createElementWithClassName('div', 'title-area');
458 this.safe_.appendChild(this.nodeTitleArea_);
459
460 this.nodeFileLink_ = createActionLink(this.openFile_.bind(this));
461 this.nodeFileLink_.className = 'name';
462 this.nodeTitleArea_.appendChild(this.nodeFileLink_);
463
464 this.nodeFileName_ = createElementWithClassName('span', 'name');
465 this.nodeTitleArea_.appendChild(this.nodeFileName_);
466
467 this.nodeStatus_ = createElementWithClassName('span', 'status');
468 this.nodeTitleArea_.appendChild(this.nodeStatus_);
469
470 var nodeURLDiv = createElementWithClassName('div', 'url-container');
471 this.safe_.appendChild(nodeURLDiv);
472
473 this.nodeURL_ = createElementWithClassName('a', 'src-url');
474 this.nodeURL_.target = '_blank';
475 nodeURLDiv.appendChild(this.nodeURL_);
476
477 // Controls.
478 this.nodeControls_ = createElementWithClassName('div', 'controls');
479 this.safe_.appendChild(this.nodeControls_);
480
481 // We don't need 'show in folder' in chromium os. See download_ui.cc and
482 // http://code.google.com/p/chromium-os/issues/detail?id=916.
483 if (loadTimeData.valueExists('control_showinfolder')) {
484 this.controlShow_ = createActionLink(this.show_.bind(this),
485 loadTimeData.getString('control_showinfolder'));
486 this.nodeControls_.appendChild(this.controlShow_);
487 } else {
488 this.controlShow_ = null;
489 }
490
491 this.controlRetry_ = document.createElement('a');
492 this.controlRetry_.download = '';
493 this.controlRetry_.textContent = loadTimeData.getString('control_retry');
494 this.nodeControls_.appendChild(this.controlRetry_);
495
496 // Pause/Resume are a toggle.
497 this.controlPause_ = createActionLink(this.pause_.bind(this),
498 loadTimeData.getString('control_pause'));
499 this.nodeControls_.appendChild(this.controlPause_);
500
501 this.controlResume_ = createActionLink(this.resume_.bind(this),
502 loadTimeData.getString('control_resume'));
503 this.nodeControls_.appendChild(this.controlResume_);
504
505 if (loadTimeData.getBoolean('allow_deleting_history')) {
506 this.controlRemove_ = createActionLink(this.remove_.bind(this),
507 loadTimeData.getString('control_removefromlist'));
508 this.controlRemove_.classList.add('control-remove-link');
509 this.nodeControls_.appendChild(this.controlRemove_);
510 }
511
512 this.controlCancel_ = createActionLink(this.cancel_.bind(this),
513 loadTimeData.getString('control_cancel'));
514 this.nodeControls_.appendChild(this.controlCancel_);
515
516 this.controlByExtension_ = document.createElement('span');
517 this.nodeControls_.appendChild(this.controlByExtension_);
518
519 // Container for 'unsafe download' UI.
520 this.danger_ = createElementWithClassName('div', 'show-dangerous');
521 this.node.appendChild(this.danger_);
522
523 this.dangerNodeImg_ = createElementWithClassName('img', 'icon');
524 this.dangerNodeImg_.alt = '';
525 this.danger_.appendChild(this.dangerNodeImg_);
526
527 this.dangerDesc_ = document.createElement('div');
528 this.danger_.appendChild(this.dangerDesc_);
529
530 // Buttons for the malicious case.
531 this.malwareNodeControls_ = createElementWithClassName('div', 'controls');
532 this.malwareSave_ = createActionLink(
533 this.saveDangerous_.bind(this),
534 loadTimeData.getString('danger_restore'));
535 this.malwareNodeControls_.appendChild(this.malwareSave_);
536 this.malwareDiscard_ = createActionLink(
537 this.discardDangerous_.bind(this),
538 loadTimeData.getString('control_removefromlist'));
539 this.malwareNodeControls_.appendChild(this.malwareDiscard_);
540 this.danger_.appendChild(this.malwareNodeControls_);
541
542 // Buttons for the dangerous but not malicious case.
543 this.dangerSave_ = createButton(
544 this.saveDangerous_.bind(this),
545 loadTimeData.getString('danger_save'));
546 this.danger_.appendChild(this.dangerSave_);
547
548 this.dangerDiscard_ = createButton(
549 this.discardDangerous_.bind(this),
550 loadTimeData.getString('danger_discard'));
551 this.danger_.appendChild(this.dangerDiscard_);
552
553 // Update member vars.
554 this.update(download);
555 }
556
557 /**
558 * The states a download can be in. These correspond to states defined in
559 * DownloadsDOMHandler::CreateDownloadItemValue
560 * @enum {string}
561 */
562 Download.States = {
563 IN_PROGRESS: 'IN_PROGRESS',
564 CANCELLED: 'CANCELLED',
565 COMPLETE: 'COMPLETE',
566 PAUSED: 'PAUSED',
567 DANGEROUS: 'DANGEROUS',
568 INTERRUPTED: 'INTERRUPTED',
569 };
570
571 /**
572 * Explains why a download is in DANGEROUS state.
573 * @enum {string}
574 */
575 Download.DangerType = {
576 NOT_DANGEROUS: 'NOT_DANGEROUS',
577 DANGEROUS_FILE: 'DANGEROUS_FILE',
578 DANGEROUS_URL: 'DANGEROUS_URL',
579 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
580 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
581 DANGEROUS_HOST: 'DANGEROUS_HOST',
582 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
583 };
584
585 /**
586 * @param {number} a Some float.
587 * @param {number} b Some float.
588 * @param {number=} opt_pct Percent of min(a,b).
589 * @return {boolean} true if a is within opt_pct percent of b.
590 */
591 function floatEq(a, b, opt_pct) {
592 return Math.abs(a - b) < (Math.min(a, b) * (opt_pct || 1.0) / 100.0);
593 }
594
595 /** Constants and "constants" for the progress meter. */
596 Download.Progress = {
597 START_ANGLE: -0.5 * Math.PI,
598 SIDE: 48,
599 };
600
601 /***/
602 Download.Progress.HALF = Download.Progress.SIDE / 2;
603
604 function computeDownloadProgress() {
605 if (floatEq(Download.Progress.scale, window.devicePixelRatio)) {
606 // Zooming in or out multiple times then typing Ctrl+0 resets the zoom level
607 // directly to 1x, which fires the matchMedia event multiple times.
608 return;
609 }
610 Download.Progress.scale = window.devicePixelRatio;
611 Download.Progress.width = Download.Progress.SIDE * Download.Progress.scale;
612 Download.Progress.height = Download.Progress.SIDE * Download.Progress.scale;
613 Download.Progress.radius = Download.Progress.HALF * Download.Progress.scale;
614 Download.Progress.centerX = Download.Progress.HALF * Download.Progress.scale;
615 Download.Progress.centerY = Download.Progress.HALF * Download.Progress.scale;
616 }
617 computeDownloadProgress();
618
619 // Listens for when device-pixel-ratio changes between any zoom level.
620 [0.3, 0.4, 0.6, 0.7, 0.8, 0.95, 1.05, 1.2, 1.4, 1.6, 1.9, 2.2, 2.7, 3.5, 4.5
621 ].forEach(function(scale) {
622 var media = '(-webkit-min-device-pixel-ratio:' + scale + ')';
623 window.matchMedia(media).addListener(computeDownloadProgress);
624 });
625
626 /**
627 * Updates the download to reflect new data.
628 * @param {DownloadItem} download Updated info about this download.
629 */
630 Download.prototype.update = function(download) {
631 this.id_ = download.id;
632 this.filePath_ = download.file_path;
633 this.fileUrl_ = download.file_url;
634 this.fileName_ = download.file_name;
635 this.url_ = download.url;
636 this.state_ = download.state;
637 this.fileExternallyRemoved_ = download.file_externally_removed;
638 this.dangerType_ = download.danger_type;
639 this.lastReasonDescription_ = download.last_reason_text;
640 this.byExtensionId_ = download.by_ext_id;
641 this.byExtensionName_ = download.by_ext_name;
642
643 this.since_ = download.since_string;
644 this.date_ = download.date_string;
645
646 // See DownloadItem::PercentComplete
647 this.percent_ = Math.max(download.percent, 0);
648 this.progressStatusText_ = download.progress_status_text;
649 this.received_ = download.received;
650
651 if (this.state_ == Download.States.DANGEROUS) {
652 this.updateDangerousFile();
653 } else {
654 downloads.scheduleIconLoad(this.nodeImg_,
655 'chrome://fileicon/' +
656 encodeURIComponent(this.filePath_) +
657 '?scale=' + window.devicePixelRatio + 'x');
658
659 if (this.state_ == Download.States.COMPLETE &&
660 !this.fileExternallyRemoved_) {
661 this.nodeFileLink_.textContent = this.fileName_;
662 this.nodeFileLink_.href = this.fileUrl_;
663 this.nodeFileLink_.oncontextmenu = null;
664 } else if (this.nodeFileName_.textContent != this.fileName_) {
665 this.nodeFileName_.textContent = this.fileName_;
666 }
667 if (this.state_ == Download.States.INTERRUPTED) {
668 this.nodeFileName_.classList.add('interrupted');
669 } else if (this.nodeFileName_.classList.contains('interrupted')) {
670 this.nodeFileName_.classList.remove('interrupted');
671 }
672
673 var completelyOnDisk = this.state_ == Download.States.COMPLETE &&
674 !this.fileExternallyRemoved_;
675 this.nodeFileName_.hidden = completelyOnDisk;
676 this.nodeFileLink_.hidden = !completelyOnDisk;
677
678 if (this.state_ == Download.States.IN_PROGRESS) {
679 this.nodeProgressForeground_.style.display = 'block';
680 this.nodeProgressBackground_.style.display = 'block';
681 this.nodeProgressForeground_.width = Download.Progress.width;
682 this.nodeProgressForeground_.height = Download.Progress.height;
683
684 var foregroundImage = (window.devicePixelRatio < 2) ?
685 downloads.progressForeground1_ : downloads.progressForeground2_;
686
687 // Draw a pie-slice for the progress.
688 this.canvasProgress_.globalCompositeOperation = 'copy';
689 this.canvasProgress_.drawImage(
690 foregroundImage,
691 0, 0, // sx, sy
692 foregroundImage.width,
693 foregroundImage.height,
694 0, 0, // x, y
695 Download.Progress.width, Download.Progress.height);
696 this.canvasProgress_.globalCompositeOperation = 'destination-in';
697 this.canvasProgress_.beginPath();
698 this.canvasProgress_.moveTo(Download.Progress.centerX,
699 Download.Progress.centerY);
700
701 // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
702 this.canvasProgress_.arc(Download.Progress.centerX,
703 Download.Progress.centerY,
704 Download.Progress.radius,
705 Download.Progress.START_ANGLE,
706 Download.Progress.START_ANGLE + Math.PI * 0.02 *
707 Number(this.percent_),
708 false);
709
710 this.canvasProgress_.lineTo(Download.Progress.centerX,
711 Download.Progress.centerY);
712 this.canvasProgress_.fill();
713 this.canvasProgress_.closePath();
714 } else if (this.nodeProgressBackground_) {
715 this.nodeProgressForeground_.style.display = 'none';
716 this.nodeProgressBackground_.style.display = 'none';
717 }
718
719 if (this.controlShow_)
720 this.controlShow_.hidden = !completelyOnDisk;
721 this.controlRetry_.hidden = !download.retry;
722 this.controlRetry_.href = this.url_;
723 this.controlPause_.hidden = this.state_ != Download.States.IN_PROGRESS;
724 this.controlResume_.hidden = !download.resume;
725 var showCancel = this.state_ == Download.States.IN_PROGRESS ||
726 this.state_ == Download.States.PAUSED;
727 this.controlCancel_.hidden = !showCancel;
728 if (this.controlRemove_)
729 this.controlRemove_.hidden = showCancel;
730
731 if (this.byExtensionId_ && this.byExtensionName_) {
732 // Format 'control_by_extension' with a link instead of plain text by
733 // splitting the formatted string into pieces.
734 var slug = 'XXXXX';
735 var formatted = loadTimeData.getStringF('control_by_extension', slug);
736 var slugIndex = formatted.indexOf(slug);
737 this.controlByExtension_.textContent = formatted.substr(0, slugIndex);
738 this.controlByExtensionLink_ = document.createElement('a');
739 this.controlByExtensionLink_.href =
740 'chrome://extensions#' + this.byExtensionId_;
741 this.controlByExtensionLink_.textContent = this.byExtensionName_;
742 this.controlByExtension_.appendChild(this.controlByExtensionLink_);
743 if (slugIndex < (formatted.length - slug.length))
744 this.controlByExtension_.appendChild(document.createTextNode(
745 formatted.substr(slugIndex + 1)));
746 }
747
748 this.nodeSince_.textContent = this.since_;
749 this.nodeDate_.textContent = this.date_;
750 // Don't unnecessarily update the url, as doing so will remove any
751 // text selection the user has started (http://crbug.com/44982).
752 if (this.nodeURL_.textContent != this.url_) {
753 this.nodeURL_.textContent = this.url_;
754 this.nodeURL_.href = this.url_;
755 }
756 this.nodeStatus_.textContent = this.getStatusText_();
757
758 this.danger_.style.display = 'none';
759 this.safe_.style.display = 'block';
760 }
761 };
762
763 /**
764 * Decorates the icons, strings, and buttons for a download to reflect the
765 * danger level of a file. Dangerous & malicious files are treated differently.
766 */
767 Download.prototype.updateDangerousFile = function() {
768 switch (this.dangerType_) {
769 case Download.DangerType.DANGEROUS_FILE: {
770 this.dangerDesc_.textContent = loadTimeData.getStringF(
771 'danger_file_desc', this.fileName_);
772 break;
773 }
774 case Download.DangerType.DANGEROUS_URL: {
775 this.dangerDesc_.textContent = loadTimeData.getString('danger_url_desc');
776 break;
777 }
778 case Download.DangerType.DANGEROUS_CONTENT: // Fall through.
779 case Download.DangerType.DANGEROUS_HOST: {
780 this.dangerDesc_.textContent = loadTimeData.getStringF(
781 'danger_content_desc', this.fileName_);
782 break;
783 }
784 case Download.DangerType.UNCOMMON_CONTENT: {
785 this.dangerDesc_.textContent = loadTimeData.getStringF(
786 'danger_uncommon_desc', this.fileName_);
787 break;
788 }
789 case Download.DangerType.POTENTIALLY_UNWANTED: {
790 this.dangerDesc_.textContent = loadTimeData.getStringF(
791 'danger_settings_desc', this.fileName_);
792 break;
793 }
794 }
795
796 if (this.dangerType_ == Download.DangerType.DANGEROUS_FILE) {
797 downloads.scheduleIconLoad(
798 this.dangerNodeImg_,
799 'chrome://theme/IDR_WARNING?scale=' + window.devicePixelRatio + 'x');
800 } else {
801 downloads.scheduleIconLoad(
802 this.dangerNodeImg_,
803 'chrome://theme/IDR_SAFEBROWSING_WARNING?scale=' +
804 window.devicePixelRatio + 'x');
805 this.dangerDesc_.className = 'malware-description';
806 }
807
808 if (this.dangerType_ == Download.DangerType.DANGEROUS_CONTENT ||
809 this.dangerType_ == Download.DangerType.DANGEROUS_HOST ||
810 this.dangerType_ == Download.DangerType.DANGEROUS_URL ||
811 this.dangerType_ == Download.DangerType.POTENTIALLY_UNWANTED) {
812 this.malwareNodeControls_.style.display = 'block';
813 this.dangerDiscard_.style.display = 'none';
814 this.dangerSave_.style.display = 'none';
815 } else {
816 this.malwareNodeControls_.style.display = 'none';
817 this.dangerDiscard_.style.display = 'inline';
818 this.dangerSave_.style.display = 'inline';
819 }
820
821 this.danger_.style.display = 'block';
822 this.safe_.style.display = 'none';
823 };
824
825 /** Removes applicable bits from the DOM in preparation for deletion. */
826 Download.prototype.clear = function() {
827 this.safe_.ondragstart = null;
828 this.nodeFileLink_.onclick = null;
829 if (this.controlShow_) {
830 this.controlShow_.onclick = null;
831 }
832 this.controlCancel_.onclick = null;
833 this.controlPause_.onclick = null;
834 this.controlResume_.onclick = null;
835 this.dangerDiscard_.onclick = null;
836 this.dangerSave_.onclick = null;
837 this.malwareDiscard_.onclick = null;
838 this.malwareSave_.onclick = null;
839
840 this.node.innerHTML = '';
841 };
842
843 /**
844 * @private
845 * @return {string} User-visible status update text.
846 */
847 Download.prototype.getStatusText_ = function() {
848 switch (this.state_) {
849 case Download.States.IN_PROGRESS:
850 return this.progressStatusText_;
851 case Download.States.CANCELLED:
852 return loadTimeData.getString('status_cancelled');
853 case Download.States.PAUSED:
854 return loadTimeData.getString('status_paused');
855 case Download.States.DANGEROUS:
856 // danger_url_desc is also used by DANGEROUS_CONTENT.
857 var desc = this.dangerType_ == Download.DangerType.DANGEROUS_FILE ?
858 'danger_file_desc' : 'danger_url_desc';
859 return loadTimeData.getString(desc);
860 case Download.States.INTERRUPTED:
861 return this.lastReasonDescription_;
862 case Download.States.COMPLETE:
863 return this.fileExternallyRemoved_ ?
864 loadTimeData.getString('status_removed') : '';
865 }
866 assertNotReached();
867 return '';
868 };
869
870 /**
871 * Tells the backend to initiate a drag, allowing users to drag
872 * files from the download page and have them appear as native file
873 * drags.
874 * @return {boolean} Returns false to prevent the default action.
875 * @private
876 */
877 Download.prototype.drag_ = function() {
878 chrome.send('drag', [this.id_]);
879 return false;
880 };
881
882 /**
883 * Tells the backend to open this file.
884 * @return {boolean} Returns false to prevent the default action.
885 * @private
886 */
887 Download.prototype.openFile_ = function() {
888 chrome.send('openFile', [this.id_]);
889 return false;
890 };
891
892 /**
893 * Tells the backend that the user chose to save a dangerous file.
894 * @return {boolean} Returns false to prevent the default action.
895 * @private
896 */
897 Download.prototype.saveDangerous_ = function() {
898 chrome.send('saveDangerous', [this.id_]);
899 return false;
900 };
901
902 /**
903 * Tells the backend that the user chose to discard a dangerous file.
904 * @return {boolean} Returns false to prevent the default action.
905 * @private
906 */
907 Download.prototype.discardDangerous_ = function() {
908 chrome.send('discardDangerous', [this.id_]);
909 downloads.remove(this.id_);
910 return false;
911 };
912
913 /**
914 * Tells the backend to show the file in explorer.
915 * @return {boolean} Returns false to prevent the default action.
916 * @private
917 */
918 Download.prototype.show_ = function() {
919 chrome.send('show', [this.id_]);
920 return false;
921 };
922
923 /**
924 * Tells the backend to pause this download.
925 * @return {boolean} Returns false to prevent the default action.
926 * @private
927 */
928 Download.prototype.pause_ = function() {
929 chrome.send('pause', [this.id_]);
930 return false;
931 };
932
933 /**
934 * Tells the backend to resume this download.
935 * @return {boolean} Returns false to prevent the default action.
936 * @private
937 */
938 Download.prototype.resume_ = function() {
939 chrome.send('resume', [this.id_]);
940 return false;
941 };
942
943 /**
944 * Tells the backend to remove this download from history and download shelf.
945 * @return {boolean} Returns false to prevent the default action.
946 * @private
947 */
948 Download.prototype.remove_ = function() {
949 assert(loadTimeData.getBoolean('allow_deleting_history'));
950 chrome.send('remove', [this.id_]);
951 return false;
952 };
953
954 /**
955 * Tells the backend to cancel this download.
956 * @return {boolean} Returns false to prevent the default action.
957 * @private
958 */
959 Download.prototype.cancel_ = function() {
960 chrome.send('cancel', [this.id_]);
961 return false;
962 };
963
964 ///////////////////////////////////////////////////////////////////////////////
965 // Page:
966 var downloads, resultsTimeout;
967
968 // TODO(benjhayden): Rename Downloads to DownloadManager, downloads to
969 // downloadManager or theDownloadManager or DownloadManager.get() to prevent
970 // confusing Downloads with Download.
971
972 /**
973 * The FIFO array that stores updates of download files to be appeared
974 * on the download page. It is guaranteed that the updates in this array
975 * are reflected to the download page in a FIFO order.
976 */
977 var fifoResults;
978
979 function load() {
980 chrome.send('onPageLoaded');
981 fifoResults = [];
982 downloads = new Downloads();
983 $('term').focus();
984 setSearch('');
985
986 $('clear-all').onclick = function() {
987 chrome.send('clearAll');
988 };
989
990 $('open-downloads-folder').onclick = function() {
991 chrome.send('openDownloadsFolder');
992 };
993
994 $('term').onsearch = function(e) {
995 setSearch($('term').value);
996 };
997 }
998
999 function setSearch(searchText) {
1000 fifoResults.length = 0;
1001 downloads.setSearchText(searchText);
1002 searchText = searchText.toString().match(/(?:[^\s"]+|"[^"]*")+/g);
1003 if (searchText) {
1004 searchText = searchText.map(function(term) {
1005 // strip quotes
1006 return (term.match(/\s/) &&
1007 term[0].match(/["']/) &&
1008 term[term.length - 1] == term[0]) ?
1009 term.substr(1, term.length - 2) : term;
1010 });
1011 } else {
1012 searchText = [];
1013 }
1014 chrome.send('getDownloads', searchText);
1015 }
1016
1017 function clearAll() {
1018 if (!loadTimeData.getBoolean('allow_deleting_history'))
1019 return;
1020
1021 fifoResults.length = 0;
1022 downloads.clear();
1023 downloads.setSearchText('');
1024 chrome.send('clearAll');
1025 }
1026
1027 ///////////////////////////////////////////////////////////////////////////////
1028 // Chrome callbacks:
1029 /**
1030 * Our history system calls this function with results from searches or when
1031 * downloads are added or removed.
1032 * @param {Array<Object>} results List of updates.
1033 */
1034 function downloadsList(results) {
1035 if (downloads && downloads.isUpdateNeeded(results)) {
1036 if (resultsTimeout)
1037 clearTimeout(resultsTimeout);
1038 fifoResults.length = 0;
1039 downloads.clear();
1040 downloadUpdated(results);
1041 }
1042 downloads.updateResults();
1043 downloads.updateSummary();
1044 }
1045
1046 /**
1047 * When a download is updated (progress, state change), this is called.
1048 * @param {Array<Object>} results List of updates for the download process.
1049 */
1050 function downloadUpdated(results) {
1051 // Sometimes this can get called too early.
1052 if (!downloads)
1053 return;
1054
1055 fifoResults = fifoResults.concat(results);
1056 tryDownloadUpdatedPeriodically();
1057 }
1058
1059 /**
1060 * Try to reflect as much updates as possible within 50ms.
1061 * This function is scheduled again and again until all updates are reflected.
1062 */
1063 function tryDownloadUpdatedPeriodically() {
1064 var start = Date.now();
1065 while (fifoResults.length) {
1066 var result = fifoResults.shift();
1067 downloads.updated(result);
1068 // Do as much as we can in 50ms.
1069 if (Date.now() - start > 50) {
1070 clearTimeout(resultsTimeout);
1071 resultsTimeout = setTimeout(tryDownloadUpdatedPeriodically, 5);
1072 break;
1073 }
1074 }
1075 }
1076
1077 // Add handlers to HTML elements.
1078 window.addEventListener('DOMContentLoaded', load);
OLDNEW
« no previous file with comments | « chrome/browser/resources/downloads/downloads.html ('k') | chrome/browser/resources/downloads/externs.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698