Home | History | Annotate | Download | only in resources
      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>
      8 body {
      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 
     29 html[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 
     49 html[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 
     71 html[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 
     92 html[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 
    104 html[dir=rtl] .download.otr > .safe,
    105 html[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 
    122 html[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
    182 function $(o) {return document.getElementById(o);}
    183 
    184 /**
    185  * Sets the display style of a node.
    186  */
    187 function showInline(node, isShow) {
    188   node.style.display = isShow ? 'inline' : 'none';
    189 }
    190 
    191 function 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  */
    200 function 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  */
    211 function 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  */
    224 function 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  */
    237 function 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  */
    252 Downloads.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  */
    276 Downloads.prototype.setSearchText = function(searchText) {
    277   this.searchText_ = searchText;
    278 }
    279 
    280 /**
    281  * Update the summary block above the results
    282  */
    283 Downloads.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  */
    306 Downloads.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  */
    324 Downloads.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  */
    333 Downloads.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  */
    346 function 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  */
    462 Download.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  */
    474 Download.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  */
    483 Download.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  */
    497 Download.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  */
    597 Download.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  */
    614 Download.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  */
    638 Download.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  */
    646 Download.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  */
    654 Download.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  */
    662 Download.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  */
    671 Download.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  */
    679 Download.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  */
    695 Download.prototype.cancel_ = function() {
    696   chrome.send('cancel', [this.id_.toString()]);
    697   return false;
    698 }
    699 
    700 ///////////////////////////////////////////////////////////////////////////////
    701 // Page:
    702 var downloads, localStrings, resultsTimeout;
    703 
    704 function load() {
    705   localStrings = new LocalStrings();
    706   downloads = new Downloads();
    707   $('term').focus();
    708   setSearch('');
    709 }
    710 
    711 function setSearch(searchText) {
    712   downloads.clear();
    713   downloads.setSearchText(searchText);
    714   chrome.send('getDownloads', [searchText.toString()]);
    715 }
    716 
    717 function 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  */
    730 function 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  */
    742 function 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