• 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 type="text/css">
8div.header {
9  border-bottom: 1px solid #7289E2;
10  padding: 8px;
11  margin: 0;
12  width: 100%;
13  left: 0;
14  top: 0;
15  height: 32px;
16  position: absolute;
17  box-sizing: border-box;
18  background-image: -webkit-linear-gradient(#D0DAF8, #A6BAF7);
19  border-bottom-color: #999;
20  border-bottom-width: 1px;
21  border-left-color: #999;
22  border-left-width: 1px;
23  border-right-color: #999;
24  border-right-width: 1px;
25  color: black;
26}
27
28*:-khtml-drag {
29  background-color: rgba(238,238,238, 0.5);
30}
31
32*[draggable] {
33  -khtml-user-drag: element;
34  cursor: move;
35}
36
37ul.downloadlist {
38  list-style-type: none;
39  margin: 0;
40  padding: 0;
41  position: relative;
42}
43
44.menuicon {
45  position: absolute;
46  right: 9px;
47  top: 11px;
48  height: 100%;
49  width: 5px;
50  margin-left: 0;
51  background: url('chrome://resources/images/active_downloads_menu.png');
52  margin-top: 5px;
53  background-repeat: no-repeat;
54}
55
56.menubutton {
57  position: absolute;
58  margin-top: -36px;
59  right: 0;
60  height: 35px;
61  width: 24px;
62  border-bottom: 1px solid #CCC;
63  cursor: pointer;
64}
65
66.rowbg {
67  border-bottom: 1px solid #CCC;
68  background: -webkit-gradient(linear, left top, left bottom,
69    from(#f3f3f3), to(#ebebeb), color-stop(0.8, #ededed));
70}
71
72.rowbg:hover {
73  background: -webkit-gradient(linear, left top, left bottom,
74    from(#fdfdfd), to(#f1f1f1), color-stop(0.8, #f5f5f5));
75}
76
77.rowbg:active {
78  background: -webkit-gradient(linear, left top, left bottom,
79    from(#d2e0f0), to(#dee6f0), color-stop(0.1, #e3e9f0));
80}
81
82.downloadrow {
83  height: 36px;
84}
85
86.rowbutton {
87  padding: 5px 5px 0 34px;
88  position: relative;
89  right: 24px;
90  border-right: 1px solid #CCC;
91  height: 30px;
92}
93
94.rowbutton div.icon {
95  float: left;
96  margin-top: 1px;
97  display: inline
98  position: relative;
99  width: 21px;
100  height: 17px;
101  background-repeat: no-repeat;
102}
103
104.rowbutton span.title {
105  position: relative;
106  text-overflow: ellipsis;
107  white-space: nowrap;
108  display: inline-block;
109  overflow: hidden;
110  width: 189px;
111  color: #325282;
112}
113
114.rowbutton span.downloading {
115  top: -6px;
116  font-size: .8em;
117}
118
119.rowbutton span.downloaded {
120  font-size: .8em;
121}
122
123.rowbutton span.error {
124  font-size: .6em;
125}
126
127.allowdownload {
128  margin: -10px 5px 5px 30px;
129  display: block;
130}
131
132.allowdownloadtext {
133  font-size: .6em;
134  color: #325282;
135}
136
137.confirm {
138  font-size: .6em;
139  text-decoration: underline;
140  margin-left: 5px;
141  color: #254f9b;
142  cursor: pointer;
143}
144
145.progress {
146  font-size: .6em;
147  text-align: right;
148  margin-left: auto;
149  margin-top: -5px;
150}
151
152div.columnlist {
153  width: 100%;
154  top: 0;
155  left: 0;
156  bottom: 29px;  /* space for Show All Downloads */
157  position: absolute;
158  background: #e8e8e8
159}
160
161span.showalldownloadstext {
162  color: #254f9b;
163  cursor: pointer;
164  text-decoration: underline;
165  font-size: 12px;
166  height: 15px;
167}
168
169div.showalldownloads {
170  width: 100%;
171  bottom: 0;
172  height: 29px;
173  position: absolute;
174  margin-left: -8px;
175  text-align: center;
176  background: #e8e8e8
177}
178
179.menu {
180  top: 14px;
181  right: 2px;
182  -webkit-box-shadow: rgba(0, 0, 0, 0.3) 0px 3px 3px;
183  border-bottom-left-radius: 4px 4px;
184  border-bottom-right-radius: 4px 4px;
185  border-top-left-radius: 4px 4px;
186  border-top-right-radius: 0px 0px;
187  position: absolute;
188  display: none;
189  z-index: 999;
190  background: white;
191  border-top-left-radius: 4px;
192  border: 1px solid rgba(0, 0, 0, 0.6);
193  padding: 5px;
194}
195
196.menuitem {
197  width: 100%;
198  height: 20px;
199  font-size: .8em;
200  text-align: left;
201  cursor: pointer;
202  left: 0;
203  color: #0D0052;
204  -webkit-transition: color 1.0s ease-out ;
205}
206
207.menuitem:hover {
208  text-decoration: underline;
209  color: #20c;
210  background: #ebeff9;
211  -webkit-transition: color 0.0s ease-out ;
212}
213
214div.iconmedia {
215  background: url('chrome://resources/images/icon_media.png');
216}
217
218div.iconfolder {
219  background: url('chrome://resources/images/icon_folder.png');
220}
221
222div.iconfile {
223  background: url('chrome://resources/images/icon_file.png');
224}
225
226div.iconphoto {
227  background: url('chrome://resources/images/icon_photo.png');
228}
229
230div.iconmusic {
231  background: url('chrome://resources/images/icon_media.png');
232}
233
234</style>
235<script src="shared/js/local_strings.js"></script>
236<script src="shared/js/media_common.js"></script>
237<script src="shared/js/util.js"></script>
238<script>
239
240var localStrings = null;
241var downloadRowList = null;
242
243function init() {
244  localStrings = new LocalStrings();
245  initTestHarness();
246
247  $('header').style.display = 'none';
248
249  $('showalldownloadstext').textContent =
250      localStrings.getString('showalldownloads');
251
252  downloadRowList = new DownloadRowList();
253  chrome.send('getDownloads', []);
254}
255
256/**
257 * Testing. Allow this page to be loaded in a browser.
258 * Create stubs for localStrings and chrome.send.
259 */
260var testHarnessEnabled = false;
261function initTestHarness() {
262  if (testHarnessEnabled) {
263    localStrings = {
264      getString: function(name) {
265        if (name == 'showalldownloads')
266          return 'Show All Downloads';
267        if (name == 'allowdownload')
268          return 'Allow Download?';
269        if (name == 'confirmyes')
270          return 'Yes';
271        if (name == 'confirmcancel')
272          return 'Cancel';
273        return name;
274      },
275      getStringF: function(name, path) {
276        return path + ' - Unknown file type.';
277      },
278    };
279    chrome.send = function(name, ary) {
280      console.log('chrome.send ' + name + ' ' + ary);
281      if (name == 'getDownloads' ||
282          (name == 'openNewFullWindow' &&
283          ary[0] == 'chrome://downloads'))
284        sendTestResults();
285    }
286  }
287}
288
289/**
290 * Create a results array with test data and call downloadsList.
291 */
292var id = 1;
293var results = [];
294function sendTestResults() {
295  results.push({
296      state: (id % 2 ? 'DANGEROUS' : 'COMPLETE'),
297      percent: (id % 2 ? 90 : 100),
298      id: id,
299      file_name: ' Test' + id + '.pdf',
300      file_path: '/home/achuith/Downloads/Test' + id + '.pdf',
301      progress_status_text : (id % 2 ?
302          'download progressing nicely' : 'download complete'),
303    });
304  id++;
305  downloadsList(results);
306}
307
308/**
309 * Current Menu.
310 */
311var menu = {
312  current_: null,
313
314  /**
315  * Close the current menu.
316  */
317  clear: function() {
318    var current = this.current_;
319    if (current) {
320      current.firstChild.style.display = 'none';
321      current.style.opacity = '';
322      this.current_ = null;
323    }
324  },
325
326  /**
327  * If it's a second click on an open menu, close the menu.
328  * Otherwise, close any other open menu and open the clicked menu.
329  */
330  clicked: function(row) {
331    var menuicon = row.menuicon;
332    if (this.current_ === menuicon) {
333      this.clear();
334      return;
335    }
336    this.clear();
337    if (menuicon.firstChild.style.display != 'block') {
338      menuicon.firstChild.style.display = 'block';
339      menuicon.style.opacity = '1';
340      menuicon.scrollIntoView();
341      this.current_ = menuicon;
342    }
343    window.event.stopPropagation();
344  },
345};
346
347/**
348 * C++ api calls.
349 */
350function downloadsList(results) {
351  downloadRowList.list(results);
352}
353
354function downloadUpdated(result) {
355  downloadRowList.update(result);
356}
357
358function showAllDownloads() {
359  chrome.send('openNewFullWindow', ['chrome://downloads']);
360  dialogClose();
361}
362
363function dialogClose() {
364  chrome.send('DialogClose', ['']);
365}
366
367/**
368 * DownloadRow contains all the elements that go into a row of the downloads
369 * list. It represents a single DownloadItem.
370 *
371 * @param {DownloadRowList} list Global DownloadRowList.
372 * @param {Object} result JSON representation of DownloadItem.
373 * @constructor
374 */
375function DownloadRow(list, result) {
376  this.path = result.file_path;
377  this.name = result.file_name;
378  this.list = list;
379  this.id = result.id;
380
381  this.createRow_(list);
382  this.createRowButton_();
383  this.createMenu_();
384}
385
386DownloadRow.prototype = {
387  /**
388  * Create the row html element and book-keeping for the row.
389  * @param {DownloadRowList} list global DownloadRowList instance.
390  * @private
391  */
392  createRow_: function(list) {
393    var row = document.createElement('li');
394    row.className = 'downloadrow';
395    row.id = this.path;
396    row.downloadRow = this;
397    list.append(row);
398
399    this.element = row;
400    this.list.downloadList.push(this);
401  },
402
403  getIconClass_: function() {
404    if (pathIsImageFile(this.path)) {
405      return 'icon iconphoto';
406    } else if (pathIsVideoFile(this.path)) {
407      return 'icon iconmedia';
408    } else if (pathIsAudioFile(this.path)) {
409      return 'icon iconmusic';
410    }
411    return 'icon iconfile';
412  },
413
414  setErrorText_: function(text) {
415    this.filename.textContent = text;
416    this.filename.className = 'error title';
417  },
418
419  supportsPdf_: function() {
420    return 'application/pdf' in navigator.mimeTypes;
421  },
422
423  openFilePath_: function() {
424    chrome.send('openNewFullWindow', ['file://' + this.path]);
425  },
426
427  /**
428  * Determine onclick behavior based on filename.
429  * @private
430  */
431  getFunctionForItem_: function() {
432    var path = this.path;
433    var self = this;
434
435    if (pathIsAudioFile(path)) {
436      return function() {
437        chrome.send('playMediaFile', [path]);
438      };
439    }
440    if (pathIsVideoFile(path)) {
441      return function() {
442        chrome.send('playMediaFile', [path]);
443      };
444    }
445    if (pathIsImageFile(path)) {
446      return function() {
447        self.openFilePath_();
448      }
449    }
450    if (pathIsHtmlFile(path)) {
451      return function() {
452        self.openFilePath_();
453      }
454    }
455    if (pathIsPdfFile(path) && this.supportsPdf_()) {
456      return function() {
457        self.openFilePath_();
458      }
459    }
460
461    return function() {
462      self.setErrorText_(localStrings.getStringF('error_unknown_file_type',
463                          self.name));
464    };
465  },
466
467  /**
468  * Create a child element.
469  *
470  * @param {string} type The type - div, span, etc.
471  * @param {string} className The class name
472  * @param {HTMLElement} parent Parent to append this child to.
473  * @param {string} textContent optional text content of child.
474  * @param {function(*)} onclick onclick function of child.
475  * @private
476  */
477  createChild_: function(type, className, parent, textContent, onclick) {
478    var elem = document.createElement(type);
479    elem.className = className;
480    if (textContent !== undefined)
481      elem.textContent = textContent;
482    elem.onclick = onclick;
483    parent.appendChild(elem);
484    return elem;
485  },
486
487  /**
488    * Create the row button for the left of the row.
489    * This contains the icon, filename and error elements.
490    * @private
491    */
492  createRowButton_: function () {
493    this.rowbutton = this.createChild_('div', 'rowbutton rowbg', this.element);
494
495    // Icon.
496    var icon = this.createChild_('div', this.getIconClass_(), this.rowbutton);
497
498    // Filename.
499    this.filename = this.createChild_('span', 'downloaded title',
500        this.rowbutton, this.name);
501  },
502
503  /**
504  * Create the menu button on the right of the row.
505  * This contains the menuicon. The menuicon contains the menu, which
506  * contains items for Open, Pause/Resume and Cancel.
507  * @private
508  */
509  createMenu_: function() {
510    var self = this;
511    this.menubutton = this.createChild_('div', 'menubutton rowbg',
512        this.element, '',
513        function() {
514          menu.clicked(self);
515        });
516
517    this.menuicon = this.createChild_('div', 'menuicon', this.menubutton);
518    this.menuicon.align = 'right';
519
520    var menudiv = this.createChild_('div', 'menu', this.menuicon);
521
522    this.open = this.createChild_('div', 'menuitem', menudiv,
523      localStrings.getString('open'), this.getFunctionForItem_());
524
525    this.pause = this.createChild_('div', 'menuitem', menudiv,
526      localStrings.getString('pause'), function() {
527                                         self.pauseToggleDownload_();
528                                       });
529
530    this.cancel = this.createChild_('div', 'menuitem', menudiv,
531      localStrings.getString('cancel'), function() {
532                                          self.cancelDownload_();
533                                        });
534
535    this.pause.style.display = 'none';
536    this.cancel.style.display = 'none';
537  },
538
539  allowDownload_: function() {
540    chrome.send('allowDownload', ['' + this.id]);
541  },
542
543  cancelDownload_: function() {
544    chrome.send('cancelDownload', ['' + this.id]);
545  },
546
547  pauseToggleDownload_: function() {
548    this.pause.textContent =
549      (this.pause.textContent == localStrings.getString('pause')) ?
550      localStrings.getString('resume') :
551      localStrings.getString('pause');
552
553    // Convert id to string before send.
554    chrome.send('pauseToggleDownload', ['' + this.id]);
555  },
556
557  resetRow_: function() {
558    this.rowbutton.onclick = '';
559    this.rowbutton.style.cursor = '';
560    this.rowbutton.setAttribute('draggable', 'false');
561  },
562
563  createAllowDownload_: function() {
564    if (this.allowdownload)
565      return;
566
567    this.allowdownload = this.createChild_('div', 'allowdownload',
568        this.rowbutton);
569
570    this.createChild_('span', 'allowdownloadtext', this.allowdownload,
571        localStrings.getString('allowdownload'));
572
573    var self = this;
574    this.createChild_('span', 'confirm', this.allowdownload,
575        localStrings.getString('confirmyes'),
576        function() {
577          self.allowDownload_();
578        });
579    this.createChild_('span', 'confirm', this.allowdownload,
580        localStrings.getString('confirmcancel'),
581        function() {
582          self.cancelDownload_();
583        });
584
585    this.resetRow_();
586    this.menubutton.onclick = '';
587  },
588
589  removeAllowDownload_: function() {
590    if (this.allowdownload) {
591      this.rowbutton.removeChild(this.allowdownload);
592      this.allowdownload = null;
593      var self = this;
594      this.menubutton.onclick = function() {
595        menu.clicked(self);
596      };
597    }
598  },
599
600  createProgress_: function() {
601    if (this.progress)
602      return;
603
604    this.progress = this.createChild_('div', 'progress', this.rowbutton);
605
606    // Menu has Pause/Cancel. Open hidden.
607    this.open.style.display = 'none';
608    this.pause.style.display = '';
609    this.cancel.style.display = '';
610  },
611
612  removeProgress_: function() {
613    if (this.progress) {
614      this.rowbutton.removeChild(this.progress);
615      this.progress = null;
616    }
617  },
618
619  updatePause_: function(result) {
620    var pause = this.pause;
621    var pauseStr = localStrings.getString('pause');
622    var resumeStr = localStrings.getString('resume');
623
624    if (pause &&
625        result.state == 'PAUSED' &&
626        pause.textContent != resumeStr) {
627      pause.textContent = resumeStr;
628    } else if (pause &&
629              result.state == 'IN_PROGRESS' &&
630              pause.textContent != pauseStr) {
631      pause.textContent = pauseStr;
632    }
633  },
634
635  updateProgress_: function(result) {
636    this.removeAllowDownload_();
637    this.createProgress_();
638    this.progress.textContent = result.progress_status_text;
639    this.updatePause_(result);
640  },
641
642  /**
643  * Called when the item has finished downloading. Switch the menu
644  * and remove the progress bar.
645  * @private
646  */
647  finishedDownloading_: function() {
648    this.filename.className = 'downloaded title';
649
650    // Menu has Open. Pause/Cancel hidden.
651    this.open.style.display = '';
652    this.pause.style.display = 'none';
653    this.cancel.style.display = 'none';
654
655    // Make rowbutton clickable.
656    this.rowbutton.onclick = this.getFunctionForItem_();
657    this.rowbutton.style.cursor = 'pointer';
658
659    // Make rowbutton draggable.
660    this.rowbutton.setAttribute('draggable', 'true');
661    var self = this;
662    this.rowbutton.addEventListener('dragstart', function(e) {
663      e.dataTransfer.effectAllowed = 'copy';
664      e.dataTransfer.setData('Text', self.path);
665      e.dataTransfer.setData('URL', 'file:///' + self.path);
666    }, false);
667
668    this.removeAllowDownload_();
669    this.removeProgress_();
670  },
671
672  /**
673  * One of the DownloadItem we are observing has updated.
674  * @param {Object} result JSON representation of DownloadItem.
675  */
676  downloadUpdated: function(result) {
677    this.filename.textContent = result.file_name;
678    this.filename.className = 'downloading title';
679
680    if (result.state == 'CANCELLED' ||
681        result.state == 'INTERRUPTED') {
682      this.list.remove(this);
683    } else if (result.state == 'DANGEROUS') {
684      this.createAllowDownload_();
685    } else if (result.percent < 100) {
686      this.updateProgress_(result);
687    } else {
688      this.finishedDownloading_();
689    }
690  },
691};
692
693/**
694 * DownloadRowList is a container for DownloadRows.
695 */
696function DownloadRowList() {
697  var downloadpath = localStrings.getString('downloadpath');
698
699  var list = document.createElement('ul');
700  list.className = 'downloadlist';
701  list.id = downloadpath;
702  this.element = list;
703  this.rows = [];
704
705  document.title = downloadpath.split('/').pop();
706
707  $('main').appendChild(list);
708}
709
710DownloadRowList.prototype = {
711
712  /**
713  * ROW_HEIGHT is height of each row.
714  * MAX_ROWS is maximum number of rows displayed (to display a new row
715  * beyond MAX_ROWS, we delete the oldest row).
716  * MIN_ROWS is the minimum number of rows displayed.
717  * numRows is the current number of rows.
718  * downloadList is the list of DownloadRow elements.
719  */
720  ROW_HEIGHT: 36,
721  MAX_ROWS: 5,
722  MIN_ROWS: 1,
723  numRows: 0,
724  downloadList: [],
725
726  numRowsOutsideRange_: function() {
727    return this.numRows > this.MIN_ROWS && this.numRows < this.MAX_ROWS;
728  },
729
730  /**
731  * Remove a row from the list, as when a download is canceled, or
732  * the the number of rows has exceeded the max allowed.
733  *
734  * @param {DownloadRow} row Row to be removed.
735  * @private
736  */
737  remove: function(row) {
738    this.downloadList.splice(this.downloadList.indexOf(row), 1);
739    this.element.removeChild(row.element);
740    row.element.downloadRow = null;
741
742    this.numRows--;
743    if (this.numRowsOutsideRange_())
744      window.resizeBy(0, -this.ROW_HEIGHT);
745  },
746
747  /**
748  * Append a new row to the list, removing the last row if we exceed the
749  * maximum allowed.
750  * @param {DownloadRow} row Row to be removed.
751  */
752  append: function(row) {
753    if (this.numRowsOutsideRange_())
754      window.resizeBy(0, this.ROW_HEIGHT);
755
756    this.numRows++;
757
758    var list = this.element;
759    if (this.numRows > this.MAX_ROWS)
760      this.remove(list.lastChild.downloadRow);
761
762    if (list.firstChild) {
763      list.insertBefore(row, list.firstChild);
764    } else {
765      list.appendChild(row);
766    }
767  },
768
769  /**
770  * Handle list callback with list of DownloadItems.
771  * @param {Array} results Array of JSONified DownloadItems.
772  */
773  list: function(results) {
774    var removeList = [];
775    removeList.pushUnique = function(element) {
776      if (this.indexOf(element) == -1)
777        this.push(element);
778    };
779
780    for (var y = 0; y < this.downloadList.length; y++) {
781      var found = false;
782      for (var x = 0; x < results.length; x++) {
783        var element = $(results[x].file_path);
784        if (this.downloadList[y].element == element) {
785          found = true;
786          break;
787        }
788      }
789      if (!found)
790        removeList.pushUnique(this.downloadList[y]);
791    }
792
793    for (var i = 0; i < results.length; i++) {
794      this.update(results[i]);
795    }
796
797    for (i = 0; i < removeList.length; i++) {
798      this.remove(removeList[i]);
799    }
800  },
801
802  /**
803  * Handle update of a DownloadItem we're observing.
804  * @param {Object} result JSON representation of DownloadItem.
805  */
806  update: function(result) {
807    var element = $(result.file_path);
808    var row = element && element.downloadRow;
809
810    if (!row &&
811        result.state != 'CANCELLED' &&
812        result.state != 'INTERRUPTED') {
813      row = new DownloadRow(this, result);
814    }
815
816    row && row.downloadUpdated(result);
817  },
818};
819
820</script>
821<body onload="init();" onclick="menu.clear()" onselectstart="return false"
822  i18n-values=".style.fontFamily:fontfamily">
823<div id="header">
824  <div id="currenttitle"></div>
825</div><br>
826<div id="main" class="columnlist"></div>
827<div id="showalldownloads" class="showalldownloads">
828  <span id="showalldownloadstext" class="showalldownloadstext"
829    onclick="showAllDownloads()"></span>
830</div>
831</body>
832</html>
833