• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!DOCTYPE HTML>
2<html i18n-values="dir:textdirection;">
3<head>
4<meta charset="utf-8">
5<title i18n-content="title"></title>
6<link rel="icon" href="../../app/theme/downloads_favicon.png">
7<style>
8body {
9  background-color: white;
10  color: black;
11  margin: 10px;
12}
13
14.header {
15  overflow: auto;
16  clear: both;
17}
18
19.header .logo {
20  float: left;
21}
22
23.header .form {
24  float: left;
25  margin-top: 22px;
26  -webkit-margin-start: 12px;
27}
28
29html[dir=rtl] .logo, html[dir=rtl] .form {
30  float: right;
31}
32
33#downloads-summary {
34  margin-top: 12px;
35  border-top: 1px solid #9cc2ef;
36  background-color: #ebeff9;
37  padding: 3px;
38  margin-bottom: 6px;
39}
40
41#downloads-summary-text {
42  font-weight: bold;
43}
44
45#downloads-summary > a {
46  float: right;
47}
48
49html[dir=rtl] #downloads-summary > a {
50  float: left;
51}
52
53#downloads-display {
54  max-width: 740px;
55}
56
57.download {
58  position: relative;
59  margin-top: 6px;
60  -webkit-margin-start: 114px;
61  -webkit-padding-start: 56px;
62  margin-bottom: 15px;
63}
64
65.date-container {
66  position: absolute;
67  left: -110px;
68  width: 110px;
69}
70
71html[dir=rtl] .date-container {
72  left: auto;
73  right: -110px;
74}
75
76.date-container .since {
77  color: black;
78}
79
80.date-container .date {
81  color: #666;
82}
83
84.download .icon {
85  position: absolute;
86  top: 2px;
87  left: 9px;
88  width: 32px;
89  height: 32px;
90}
91
92html[dir=rtl] .icon {
93  left: auto;
94  right: 9px;
95}
96
97.download.otr > .safe,
98.download.otr > .show-dangerous {
99  background: url('shared/images/otr_icon_standalone.png') no-repeat 100% 100%;
100  opacity: .66;
101  -webkit-transition: opacity .15s;
102}
103
104html[dir=rtl] .download.otr > .safe,
105html[dir=rtl] .download.otr > .show-dangerous {
106  background-position: 0% 100%;
107}
108
109.download.otr > .safe:hover,
110.download.otr > .show-dangerous:hover {
111  opacity: 1;
112}
113
114.progress {
115  position: absolute;
116  top: -6px;
117  left: 0px;
118  width: 48px;
119  height: 48px;
120}
121
122html[dir=rtl] .progress {
123  left: auto;
124  right: 0px;
125}
126
127.progress.background {
128  background: url('../../app/theme/download_progress_background32.png');
129}
130
131.progress.foreground {
132  background: url('../../app/theme/download_progress_foreground32.png');
133}
134
135.name {
136  display: none;
137  -webkit-padding-end: 16px;
138  max-width: 450px;
139  word-break: break-all;
140}
141
142.download .status {
143  display: inline;
144  color: #999;
145  white-space: nowrap;
146}
147
148.download .url {
149  color: #080;
150  max-width: 500px;
151  white-space: nowrap;
152  overflow: hidden;
153  text-overflow: ellipsis;
154}
155
156.controls a {
157  color: #777;
158  margin-right: 16px;
159}
160
161#downloads-pagination {
162  padding-top: 24px;
163  margin-left: 18px;
164}
165
166.page-navigation {
167  padding: 8px;
168  background-color: #ebeff9;
169  margin-right: 4px;
170}
171
172.footer {
173  height: 24px;
174}
175
176</style>
177<script src="shared/js/local_strings.js"></script>
178<script>
179
180///////////////////////////////////////////////////////////////////////////////
181// Helper functions
182function $(o) {return document.getElementById(o);}
183
184/**
185 * Sets the display style of a node.
186 */
187function showInline(node, isShow) {
188  node.style.display = isShow ? 'inline' : 'none';
189}
190
191function showInlineBlock(node, isShow) {
192  node.style.display = isShow ? 'inline-block' : 'none';
193}
194
195/**
196 * Creates an element of a specified type with a specified class name.
197 * @param {String} type The node type.
198 * @param {String} className The class name to use.
199 */
200function createElementWithClassName(type, className) {
201  var elm = document.createElement(type);
202  elm.className = className;
203  return elm;
204}
205
206/**
207 * Creates a link with a specified onclick handler and content
208 * @param {String} onclick The onclick handler
209 * @param {String} value The link text
210 */
211function createLink(onclick, value) {
212  var link = document.createElement('a');
213  link.onclick = onclick;
214  link.href = '#';
215  link.innerHTML = value;
216  return link;
217}
218
219/**
220 * Creates a button with a specified onclick handler and content
221 * @param {String} onclick The onclick handler
222 * @param {String} value The button text
223 */
224function createButton(onclick, value) {
225  var button = document.createElement('input');
226  button.type = 'button';
227  button.value = value;
228  button.onclick = onclick;
229  return button;
230}
231
232///////////////////////////////////////////////////////////////////////////////
233// Downloads
234/**
235 * Class to hold all the information about the visible downloads.
236 */
237function Downloads() {
238  this.downloads_ = {};
239  this.node_ = $('downloads-display');
240  this.summary_ = $('downloads-summary-text');
241  this.searchText_ = '';
242
243  // Keep track of the dates of the newest and oldest downloads so that we
244  // know where to insert them.
245  this.newestTime_ = -1;
246}
247
248/**
249 * Called when a download has been updated or added.
250 * @param {Object} download A backend download object (see downloads_ui.cc)
251 */
252Downloads.prototype.updated = function(download) {
253  var id = download.id;
254  if (!!this.downloads_[id]) {
255    this.downloads_[id].update(download);
256  } else {
257    this.downloads_[id] = new Download(download);
258    // We get downloads in display order, so we don't have to worry about
259    // maintaining correct order - we can assume that any downloads not in
260    // display order are new ones and so we can add them to the top of the
261    // list.
262    if (download.started > this.newestTime_) {
263      this.node_.insertBefore(this.downloads_[id].node, this.node_.firstChild);
264      this.newestTime_ = download.started;
265    } else {
266      this.node_.appendChild(this.downloads_[id].node);
267    }
268    this.updateDateDisplay_();
269  }
270}
271
272/**
273 * Set our display search text.
274 * @param {String} searchText The string we're searching for.
275 */
276Downloads.prototype.setSearchText = function(searchText) {
277  this.searchText_ = searchText;
278}
279
280/**
281 * Update the summary block above the results
282 */
283Downloads.prototype.updateSummary = function() {
284  if (this.searchText_) {
285    this.summary_.textContent = localStrings.getStringF('searchresultsfor',
286                                                        this.searchText_);
287  } else {
288    this.summary_.innerHTML = localStrings.getString('downloads');
289  }
290
291  var hasDownloads = false;
292  for (var i in this.downloads_) {
293    hasDownloads = true;
294    break;
295  }
296
297  if (!hasDownloads) {
298    this.node_.innerHTML = localStrings.getString('noresults');
299  }
300}
301
302/**
303 * Update the date visibility in our nodes so that no date is
304 * repeated.
305 */
306Downloads.prototype.updateDateDisplay_ = function() {
307  var dateContainers = document.getElementsByClassName('date-container');
308  var displayed = {};
309  for (var i = 0, container; container = dateContainers[i]; i++) {
310    var dateString = container.getElementsByClassName('date')[0].innerHTML;
311    if (!!displayed[dateString]) {
312      container.style.display = 'none';
313    } else {
314      displayed[dateString] = true;
315      container.style.display = 'block';
316    }
317  }
318}
319
320/**
321 * Remove a download.
322 * @param {Number} id The id of the download to remove.
323 */
324Downloads.prototype.remove = function(id) {
325  this.node_.removeChild(this.downloads_[id].node);
326  delete this.downloads_[id];
327  this.updateDateDisplay_();
328}
329
330/**
331 * Clear all downloads and reset us back to a null state.
332 */
333Downloads.prototype.clear = function() {
334  for (var id in this.downloads_) {
335    this.downloads_[id].clear();
336    this.remove(id);
337  }
338}
339
340///////////////////////////////////////////////////////////////////////////////
341// Download
342/**
343 * A download and the DOM representation for that download.
344 * @param {Object} download A backend download object (see downloads_ui.cc)
345 */
346function Download(download) {
347  // Create DOM
348  this.node = createElementWithClassName('div','download' +
349                                         (download.otr ? ' otr' : ''));
350
351  // Dates
352  this.dateContainer_ = createElementWithClassName('div', 'date-container');
353  this.node.appendChild(this.dateContainer_);
354
355  this.nodeSince_ = createElementWithClassName('div', 'since');
356  this.nodeDate_ = createElementWithClassName('div', 'date');
357  this.dateContainer_.appendChild(this.nodeSince_);
358  this.dateContainer_.appendChild(this.nodeDate_);
359
360  // Container for all 'safe download' UI.
361  this.safe_ = createElementWithClassName('div', 'safe');
362  this.safe_.ondragstart = this.drag_.bind(this);
363  this.node.appendChild(this.safe_);
364
365  if (download.state != Download.States.COMPLETE) {
366    this.nodeProgressBackground_ =
367        createElementWithClassName('div', 'progress background');
368    this.safe_.appendChild(this.nodeProgressBackground_);
369
370    this.canvasProgress_ =
371        document.getCSSCanvasContext('2d', 'canvas_' + download.id,
372            Download.Progress.width,
373            Download.Progress.height);
374
375    this.nodeProgressForeground_ =
376        createElementWithClassName('div', 'progress foreground');
377    this.nodeProgressForeground_.style.webkitMask =
378        '-webkit-canvas(canvas_'+download.id+')';
379    this.safe_.appendChild(this.nodeProgressForeground_);
380  }
381
382  this.nodeImg_ = createElementWithClassName('img', 'icon');
383  this.safe_.appendChild(this.nodeImg_);
384
385  // FileLink is used for completed downloads, otherwise we show FileName.
386  this.nodeTitleArea_ = createElementWithClassName('div', 'title-area');
387  this.safe_.appendChild(this.nodeTitleArea_);
388
389  this.nodeFileLink_ = createLink(this.openFile_.bind(this), '');
390  this.nodeFileLink_.className = 'name';
391  this.nodeFileLink_.style.display = 'none';
392  this.nodeTitleArea_.appendChild(this.nodeFileLink_);
393
394  this.nodeFileName_ = createElementWithClassName('span', 'name');
395  this.nodeFileName_.style.display = 'none';
396  this.nodeTitleArea_.appendChild(this.nodeFileName_);
397
398  this.nodeStatus_ = createElementWithClassName('span', 'status');
399  this.nodeTitleArea_.appendChild(this.nodeStatus_);
400
401  this.nodeURL_ = createElementWithClassName('div', 'url');
402  this.safe_.appendChild(this.nodeURL_);
403
404  // Controls.
405  this.nodeControls_ = createElementWithClassName('div', 'controls');
406  this.safe_.appendChild(this.nodeControls_);
407
408  // We don't need "show in folder" in chromium os. See download_ui.cc and
409  // http://code.google.com/p/chromium-os/issues/detail?id=916.
410  var showinfolder = localStrings.getString('control_showinfolder');
411  if (showinfolder) {
412    this.controlShow_ = createLink(this.show_.bind(this), showinfolder);
413    this.nodeControls_.appendChild(this.controlShow_);
414  } else {
415    this.controlShow_ = null;
416  }
417
418  this.controlRetry_ = document.createElement('a');
419  this.controlRetry_.textContent = localStrings.getString('control_retry');
420  this.nodeControls_.appendChild(this.controlRetry_);
421
422  // Pause/Resume are a toggle.
423  this.controlPause_ = createLink(this.togglePause_.bind(this),
424      localStrings.getString('control_pause'));
425  this.nodeControls_.appendChild(this.controlPause_);
426
427  this.controlResume_ = createLink(this.togglePause_.bind(this),
428      localStrings.getString('control_resume'));
429  this.nodeControls_.appendChild(this.controlResume_);
430
431  this.controlRemove_ = createLink(this.remove_.bind(this),
432      localStrings.getString('control_removefromlist'));
433  this.nodeControls_.appendChild(this.controlRemove_);
434
435  this.controlCancel_ = createLink(this.cancel_.bind(this),
436      localStrings.getString('control_cancel'));
437  this.nodeControls_.appendChild(this.controlCancel_);
438
439  // Container for 'unsafe download' UI.
440  this.danger_ = createElementWithClassName('div', 'show-dangerous');
441  this.node.appendChild(this.danger_);
442
443  this.dangerDesc_ = document.createElement('div');
444  this.danger_.appendChild(this.dangerDesc_);
445
446  this.dangerSave_ = createButton(this.saveDangerous_.bind(this),
447      localStrings.getString('danger_save'));
448  this.danger_.appendChild(this.dangerSave_);
449
450  this.dangerDiscard_ = createButton(this.discardDangerous_.bind(this),
451      localStrings.getString('danger_discard'));
452  this.danger_.appendChild(this.dangerDiscard_);
453
454  // Update member vars.
455  this.update(download);
456}
457
458/**
459 * The states a download can be in. These correspond to states defined in
460 * DownloadsDOMHandler::CreateDownloadItemValue
461 */
462Download.States = {
463  IN_PROGRESS : "IN_PROGRESS",
464  CANCELLED : "CANCELLED",
465  COMPLETE : "COMPLETE",
466  PAUSED : "PAUSED",
467  DANGEROUS : "DANGEROUS",
468  INTERRUPTED : "INTERRUPTED",
469}
470
471/**
472 * Explains why a download is in DANGEROUS state.
473 */
474Download.DangerType = {
475  NOT_DANGEROUS: "NOT_DANGEROUS",
476  DANGEROUS_FILE: "DANGEROUS_FILE",
477  DANGEROUS_URL: "DANGEROUS_URL",
478}
479
480/**
481 * Constants for the progress meter.
482 */
483Download.Progress = {
484  width : 48,
485  height : 48,
486  radius : 24,
487  centerX : 24,
488  centerY : 24,
489  base : -0.5 * Math.PI,
490  dir : false,
491}
492
493/**
494 * Updates the download to reflect new data.
495 * @param {Object} download A backend download object (see downloads_ui.cc)
496 */
497Download.prototype.update = function(download) {
498  this.id_ = download.id;
499  this.filePath_ = download.file_path;
500  this.fileName_ = download.file_name;
501  this.url_ = download.url;
502  this.state_ = download.state;
503  this.dangerType_ = download.danger_type;
504
505  this.since_ = download.since_string;
506  this.date_ = download.date_string;
507
508  // See DownloadItem::PercentComplete
509  this.percent_ = Math.max(download.percent, 0);
510  this.progressStatusText_ = download.progress_status_text;
511  this.received_ = download.received;
512
513  if (this.state_ == Download.States.DANGEROUS) {
514    if (this.dangerType_ == Download.DangerType.DANGEROUS_FILE) {
515      this.dangerDesc_.innerHTML = localStrings.getStringF('danger_file_desc',
516                                                           this.fileName_);
517    } else {
518      this.dangerDesc_.innerHTML = localStrings.getString('danger_url_desc');
519    }
520    this.danger_.style.display = 'block';
521    this.safe_.style.display = 'none';
522  } else {
523    this.nodeImg_.src = 'chrome://fileicon/' + this.filePath_;
524
525    if (this.state_ == Download.States.COMPLETE) {
526      this.nodeFileLink_.innerHTML = this.fileName_;
527      this.nodeFileLink_.href = this.filePath_;
528    } else {
529      this.nodeFileName_.innerHTML = this.fileName_;
530    }
531
532    showInline(this.nodeFileLink_, this.state_ == Download.States.COMPLETE);
533    // nodeFileName_ has to be inline-block to avoid the 'interaction' with
534    // nodeStatus_. If both are inline, it appears that their text contents
535    // are merged before the bidi algorithm is applied leading to an
536    // undesirable reordering. http://crbug.com/13216
537    showInlineBlock(this.nodeFileName_, this.state_ != Download.States.COMPLETE);
538
539    if (this.state_ == Download.States.IN_PROGRESS) {
540      this.nodeProgressForeground_.style.display = 'block';
541      this.nodeProgressBackground_.style.display = 'block';
542
543      // Draw a pie-slice for the progress.
544      this.canvasProgress_.clearRect(0, 0,
545                                     Download.Progress.width,
546                                     Download.Progress.height);
547      this.canvasProgress_.beginPath();
548      this.canvasProgress_.moveTo(Download.Progress.centerX,
549                                  Download.Progress.centerY);
550
551      // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
552      this.canvasProgress_.arc(Download.Progress.centerX,
553                               Download.Progress.centerY,
554                               Download.Progress.radius,
555                               Download.Progress.base,
556                               Download.Progress.base + Math.PI * 0.02 *
557                               Number(this.percent_),
558                               false);
559
560      this.canvasProgress_.lineTo(Download.Progress.centerX,
561                                  Download.Progress.centerY);
562      this.canvasProgress_.fill();
563      this.canvasProgress_.closePath();
564    } else if (this.nodeProgressBackground_) {
565      this.nodeProgressForeground_.style.display = 'none';
566      this.nodeProgressBackground_.style.display = 'none';
567    }
568
569    if (this.controlShow_) {
570      showInline(this.controlShow_, this.state_ == Download.States.COMPLETE);
571    }
572    showInline(this.controlRetry_, this.state_ == Download.States.CANCELLED);
573    this.controlRetry_.href = this.url_;
574    showInline(this.controlPause_, this.state_ == Download.States.IN_PROGRESS);
575    showInline(this.controlResume_, this.state_ == Download.States.PAUSED);
576    var showCancel = this.state_ == Download.States.IN_PROGRESS ||
577                     this.state_ == Download.States.PAUSED;
578    showInline(this.controlCancel_, showCancel);
579    showInline(this.controlRemove_, !showCancel);
580
581    this.nodeSince_.innerHTML = this.since_;
582    this.nodeDate_.innerHTML = this.date_;
583    // Don't unnecessarily update the url, as doing so will remove any
584    // text selection the user has started (http://crbug.com/44982).
585    if (this.nodeURL_.textContent != this.url_)
586      this.nodeURL_.textContent = this.url_;
587    this.nodeStatus_.innerHTML = this.getStatusText_();
588
589    this.danger_.style.display = 'none';
590    this.safe_.style.display = 'block';
591  }
592}
593
594/**
595 * Removes applicable bits from the DOM in preparation for deletion.
596 */
597Download.prototype.clear = function() {
598  this.safe_.ondragstart = null;
599  this.nodeFileLink_.onclick = null;
600  if (this.controlShow_) {
601    this.controlShow_.onclick = null;
602  }
603  this.controlCancel_.onclick = null;
604  this.controlPause_.onclick = null;
605  this.controlResume_.onclick = null;
606  this.dangerDiscard_.onclick = null;
607
608  this.node.innerHTML = '';
609}
610
611/**
612 * @return {String} User-visible status update text.
613 */
614Download.prototype.getStatusText_ = function() {
615  switch (this.state_) {
616    case Download.States.IN_PROGRESS:
617      return this.progressStatusText_;
618    case Download.States.CANCELLED:
619      return localStrings.getString('status_cancelled');
620    case Download.States.PAUSED:
621      return localStrings.getString('status_paused');
622    case Download.States.DANGEROUS:
623      var desc = this.dangerType_ == Download.DangerType.DANGEROUS_FILE ?
624          'danger_file_desc' : 'danger_url_desc';
625      return localStrings.getString(desc);
626    case Download.States.INTERRUPTED:
627      return localStrings.getString('status_interrupted');
628    case Download.States.COMPLETE:
629      return '';
630  }
631}
632
633/**
634 * Tells the backend to initiate a drag, allowing users to drag
635 * files from the download page and have them appear as native file
636 * drags.
637 */
638Download.prototype.drag_ = function() {
639  chrome.send('drag', [this.id_.toString()]);
640  return false;
641}
642
643/**
644 * Tells the backend to open this file.
645 */
646Download.prototype.openFile_ = function() {
647  chrome.send('openFile', [this.id_.toString()]);
648  return false;
649}
650
651/**
652 * Tells the backend that the user chose to save a dangerous file.
653 */
654Download.prototype.saveDangerous_ = function() {
655  chrome.send('saveDangerous', [this.id_.toString()]);
656  return false;
657}
658
659/**
660 * Tells the backend that the user chose to discard a dangerous file.
661 */
662Download.prototype.discardDangerous_ = function() {
663  chrome.send('discardDangerous', [this.id_.toString()]);
664  downloads.remove(this.id_);
665  return false;
666}
667
668/**
669 * Tells the backend to show the file in explorer.
670 */
671Download.prototype.show_ = function() {
672  chrome.send('show', [this.id_.toString()]);
673  return false;
674}
675
676/**
677 * Tells the backend to pause this download.
678 */
679Download.prototype.togglePause_ = function() {
680  chrome.send('togglepause', [this.id_.toString()]);
681  return false;
682}
683
684/**
685 * Tells the backend to remove this download from history and download shelf.
686 */
687 Download.prototype.remove_ = function() {
688  chrome.send('remove', [this.id_.toString()]);
689  return false;
690}
691
692/**
693 * Tells the backend to cancel this download.
694 */
695Download.prototype.cancel_ = function() {
696  chrome.send('cancel', [this.id_.toString()]);
697  return false;
698}
699
700///////////////////////////////////////////////////////////////////////////////
701// Page:
702var downloads, localStrings, resultsTimeout;
703
704function load() {
705  localStrings = new LocalStrings();
706  downloads = new Downloads();
707  $('term').focus();
708  setSearch('');
709}
710
711function setSearch(searchText) {
712  downloads.clear();
713  downloads.setSearchText(searchText);
714  chrome.send('getDownloads', [searchText.toString()]);
715}
716
717function clearAll() {
718  downloads.clear();
719  downloads.setSearchText('');
720  chrome.send('clearAll', []);
721  return false;
722}
723
724///////////////////////////////////////////////////////////////////////////////
725// Chrome callbacks:
726/**
727 * Our history system calls this function with results from searches or when
728 * downloads are added or removed.
729 */
730function downloadsList(results) {
731  if (resultsTimeout)
732    clearTimeout(resultsTimeout);
733  window.console.log('results');
734  downloads.clear();
735  downloadUpdated(results);
736  downloads.updateSummary();
737}
738
739/**
740 * When a download is updated (progress, state change), this is called.
741 */
742function downloadUpdated(results) {
743  // Sometimes this can get called too early.
744  if (!downloads)
745    return;
746
747  var start = Date.now();
748  for (var i = 0; i < results.length; i++) {
749    downloads.updated(results[i]);
750    // Do as much as we can in 50ms.
751    if (Date.now() - start > 50) {
752      clearTimeout(resultsTimeout);
753      resultsTimeout = setTimeout(downloadUpdated, 5, results.slice(i + 1));
754      break;
755    }
756  }
757}
758
759</script>
760</head>
761<body onload="load();" i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize">
762<div class="header">
763  <a href="" onclick="setSearch(''); return false;">
764    <img src="shared/images/downloads_section.png"
765         width="67" height="67" class="logo" border="0" /></a>
766  <form method="post" action=""
767      onsubmit="setSearch(this.term.value); return false;"
768      class="form">
769    <input type="text" name="term" id="term" />
770    <input type="submit" name="submit" i18n-values="value:searchbutton" />
771  </form>
772</div>
773<div class="main">
774  <div id="downloads-summary">
775    <span id="downloads-summary-text" i18n-content="downloads">Downloads</span>
776    <a id="clear-all" href="" onclick="clearAll();" i18n-content="clear_all">Clear All</a>
777  </div>
778  <div id="downloads-display"></div>
779</div>
780<div class="footer">
781</div>
782</body>
783</html>
784