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.
5 // TODO(jhawkins): Use hidden instead of showInline* and display:none.
8 * Sets the display style of a node.
9 * @param {!Element} node The target element to show or hide.
10 * @param {boolean} isShow Should the target element be visible.
12 function showInline(node, isShow) {
13 node.style.display = isShow ? 'inline' : 'none';
17 * Sets the display style of a node.
18 * @param {!Element} node The target element to show or hide.
19 * @param {boolean} isShow Should the target element be visible.
21 function showInlineBlock(node, isShow) {
22 node.style.display = isShow ? 'inline-block' : 'none';
26 * Creates a link with a specified onclick handler and content.
27 * @param {function()} onclick The onclick handler.
28 * @param {string} value The link text.
29 * @return {Element} The created link element.
31 function createLink(onclick, value) {
32 var link = document.createElement('a');
33 link.onclick = onclick;
35 link.textContent = value;
36 link.oncontextmenu = function() { return false; };
41 * Creates a button with a specified onclick handler and content.
42 * @param {function()} onclick The onclick handler.
43 * @param {string} value The button text.
44 * @return {Element} The created button.
46 function createButton(onclick, value) {
47 var button = document.createElement('input');
48 button.type = 'button';
50 button.onclick = onclick;
54 ///////////////////////////////////////////////////////////////////////////////
57 * Class to hold all the information about the visible downloads.
60 function Downloads() {
62 this.node_ = $('downloads-display');
63 this.summary_ = $('downloads-summary-text');
64 this.searchText_ = '';
66 // Keep track of the dates of the newest and oldest downloads so that we
67 // know where to insert them.
68 this.newestTime_ = -1;
70 // Icon load request queue.
71 this.iconLoadQueue_ = [];
72 this.isIconLoading_ = false;
76 * Called when a download has been updated or added.
77 * @param {Object} download A backend download object (see downloads_ui.cc)
79 Downloads.prototype.updated = function(download) {
81 if (!!this.downloads_[id]) {
82 this.downloads_[id].update(download);
84 this.downloads_[id] = new Download(download);
85 // We get downloads in display order, so we don't have to worry about
86 // maintaining correct order - we can assume that any downloads not in
87 // display order are new ones and so we can add them to the top of the
89 if (download.started > this.newestTime_) {
90 this.node_.insertBefore(this.downloads_[id].node, this.node_.firstChild);
91 this.newestTime_ = download.started;
93 this.node_.appendChild(this.downloads_[id].node);
96 // Download.prototype.update may change its nodeSince_ and nodeDate_, so
97 // update all the date displays.
98 // TODO(benjhayden) Only do this if its nodeSince_ or nodeDate_ actually did
99 // change since this may touch 150 elements and Downloads.prototype.updated
100 // may be called 150 times.
101 this.updateDateDisplay_();
105 * Set our display search text.
106 * @param {string} searchText The string we're searching for.
108 Downloads.prototype.setSearchText = function(searchText) {
109 this.searchText_ = searchText;
113 * Update the summary block above the results
115 Downloads.prototype.updateSummary = function() {
116 if (this.searchText_) {
117 this.summary_.textContent = loadTimeData.getStringF('searchresultsfor',
120 this.summary_.textContent = loadTimeData.getString('downloads');
123 var hasDownloads = false;
124 for (var i in this.downloads_) {
131 * Returns the number of downloads in the model. Used by tests.
132 * @return {integer} Returns the number of downloads shown on the page.
134 Downloads.prototype.size = function() {
135 return Object.keys(this.downloads_).length;
139 * Update the date visibility in our nodes so that no date is
143 Downloads.prototype.updateDateDisplay_ = function() {
144 var dateContainers = document.getElementsByClassName('date-container');
146 for (var i = 0, container; container = dateContainers[i]; i++) {
147 var dateString = container.getElementsByClassName('date')[0].innerHTML;
148 if (!!displayed[dateString]) {
149 container.style.display = 'none';
151 displayed[dateString] = true;
152 container.style.display = 'block';
159 * @param {number} id The id of the download to remove.
161 Downloads.prototype.remove = function(id) {
162 this.node_.removeChild(this.downloads_[id].node);
163 delete this.downloads_[id];
164 this.updateDateDisplay_();
168 * Clear all downloads and reset us back to a null state.
170 Downloads.prototype.clear = function() {
171 for (var id in this.downloads_) {
172 this.downloads_[id].clear();
178 * Schedule icon load.
179 * @param {HTMLImageElement} elem Image element that should contain the icon.
180 * @param {string} iconURL URL to the icon.
182 Downloads.prototype.scheduleIconLoad = function(elem, iconURL) {
185 // Sends request to the next icon in the queue and schedules
186 // call to itself when the icon is loaded.
187 function loadNext() {
188 self.isIconLoading_ = true;
189 while (self.iconLoadQueue_.length > 0) {
190 var request = self.iconLoadQueue_.shift();
191 var oldSrc = request.element.src;
192 request.element.onabort = request.element.onerror =
193 request.element.onload = loadNext;
194 request.element.src = request.url;
195 if (oldSrc != request.element.src)
198 self.isIconLoading_ = false;
201 // Create new request
202 var loadRequest = {element: elem, url: iconURL};
203 this.iconLoadQueue_.push(loadRequest);
205 // Start loading if none scheduled yet
206 if (!this.isIconLoading_)
211 * Returns whether the displayed list needs to be updated or not.
212 * @param {Array} downloads Array of download nodes.
213 * @return {boolean} Returns true if the displayed list is to be updated.
215 Downloads.prototype.isUpdateNeeded = function(downloads) {
217 for (var i in this.downloads_)
219 if (size != downloads.length)
221 // Since there are the same number of items in the incoming list as
222 // |this.downloads_|, there won't be any removed downloads without some
223 // downloads having been inserted. So check only for new downloads in
224 // deciding whether to update.
225 for (var i = 0; i < downloads.length; i++) {
226 if (!this.downloads_[downloads[i].id])
232 ///////////////////////////////////////////////////////////////////////////////
235 * A download and the DOM representation for that download.
236 * @param {Object} download A backend download object (see downloads_ui.cc)
239 function Download(download) {
241 this.node = createElementWithClassName(
242 'div', 'download' + (download.otr ? ' otr' : ''));
245 this.dateContainer_ = createElementWithClassName('div', 'date-container');
246 this.node.appendChild(this.dateContainer_);
248 this.nodeSince_ = createElementWithClassName('div', 'since');
249 this.nodeDate_ = createElementWithClassName('div', 'date');
250 this.dateContainer_.appendChild(this.nodeSince_);
251 this.dateContainer_.appendChild(this.nodeDate_);
253 // Container for all 'safe download' UI.
254 this.safe_ = createElementWithClassName('div', 'safe');
255 this.safe_.ondragstart = this.drag_.bind(this);
256 this.node.appendChild(this.safe_);
258 if (download.state != Download.States.COMPLETE) {
259 this.nodeProgressBackground_ =
260 createElementWithClassName('div', 'progress background');
261 this.safe_.appendChild(this.nodeProgressBackground_);
263 this.nodeProgressForeground_ =
264 createElementWithClassName('canvas', 'progress');
265 this.nodeProgressForeground_.width = Download.Progress.width;
266 this.nodeProgressForeground_.height = Download.Progress.height;
267 this.canvasProgress_ = this.nodeProgressForeground_.getContext('2d');
269 this.canvasProgressForegroundImage_ = new Image();
270 this.canvasProgressForegroundImage_.src =
271 'chrome://theme/IDR_DOWNLOAD_PROGRESS_FOREGROUND_32@' +
272 window.devicePixelRatio + 'x';
273 this.safe_.appendChild(this.nodeProgressForeground_);
276 this.nodeImg_ = createElementWithClassName('img', 'icon');
277 this.safe_.appendChild(this.nodeImg_);
279 // FileLink is used for completed downloads, otherwise we show FileName.
280 this.nodeTitleArea_ = createElementWithClassName('div', 'title-area');
281 this.safe_.appendChild(this.nodeTitleArea_);
283 this.nodeFileLink_ = createLink(this.openFile_.bind(this), '');
284 this.nodeFileLink_.className = 'name';
285 this.nodeFileLink_.style.display = 'none';
286 this.nodeTitleArea_.appendChild(this.nodeFileLink_);
288 this.nodeFileName_ = createElementWithClassName('span', 'name');
289 this.nodeFileName_.style.display = 'none';
290 this.nodeTitleArea_.appendChild(this.nodeFileName_);
292 this.nodeStatus_ = createElementWithClassName('span', 'status');
293 this.nodeTitleArea_.appendChild(this.nodeStatus_);
295 var nodeURLDiv = createElementWithClassName('div', 'url-container');
296 this.safe_.appendChild(nodeURLDiv);
298 this.nodeURL_ = createElementWithClassName('a', 'src-url');
299 this.nodeURL_.target = '_blank';
300 nodeURLDiv.appendChild(this.nodeURL_);
303 this.nodeControls_ = createElementWithClassName('div', 'controls');
304 this.safe_.appendChild(this.nodeControls_);
306 // We don't need 'show in folder' in chromium os. See download_ui.cc and
307 // http://code.google.com/p/chromium-os/issues/detail?id=916.
308 if (loadTimeData.valueExists('control_showinfolder')) {
309 this.controlShow_ = createLink(this.show_.bind(this),
310 loadTimeData.getString('control_showinfolder'));
311 this.nodeControls_.appendChild(this.controlShow_);
313 this.controlShow_ = null;
316 this.controlRetry_ = document.createElement('a');
317 this.controlRetry_.download = '';
318 this.controlRetry_.textContent = loadTimeData.getString('control_retry');
319 this.nodeControls_.appendChild(this.controlRetry_);
321 // Pause/Resume are a toggle.
322 this.controlPause_ = createLink(this.pause_.bind(this),
323 loadTimeData.getString('control_pause'));
324 this.nodeControls_.appendChild(this.controlPause_);
326 this.controlResume_ = createLink(this.resume_.bind(this),
327 loadTimeData.getString('control_resume'));
328 this.nodeControls_.appendChild(this.controlResume_);
330 // Anchors <a> don't support the "disabled" property.
331 if (loadTimeData.getBoolean('allow_deleting_history')) {
332 this.controlRemove_ = createLink(this.remove_.bind(this),
333 loadTimeData.getString('control_removefromlist'));
334 this.controlRemove_.classList.add('control-remove-link');
336 this.controlRemove_ = document.createElement('span');
337 this.controlRemove_.classList.add('disabled-link');
338 var text = document.createTextNode(
339 loadTimeData.getString('control_removefromlist'));
340 this.controlRemove_.appendChild(text);
342 if (!loadTimeData.getBoolean('show_delete_history'))
343 this.controlRemove_.hidden = true;
345 this.nodeControls_.appendChild(this.controlRemove_);
347 this.controlCancel_ = createLink(this.cancel_.bind(this),
348 loadTimeData.getString('control_cancel'));
349 this.nodeControls_.appendChild(this.controlCancel_);
351 this.controlByExtension_ = document.createElement('span');
352 this.nodeControls_.appendChild(this.controlByExtension_);
354 // Container for 'unsafe download' UI.
355 this.danger_ = createElementWithClassName('div', 'show-dangerous');
356 this.node.appendChild(this.danger_);
358 this.dangerNodeImg_ = createElementWithClassName('img', 'icon');
359 this.danger_.appendChild(this.dangerNodeImg_);
361 this.dangerDesc_ = document.createElement('div');
362 this.danger_.appendChild(this.dangerDesc_);
364 // Buttons for the malicious case.
365 this.malwareNodeControls_ = createElementWithClassName('div', 'controls');
366 this.malwareSave_ = createLink(
367 this.saveDangerous_.bind(this),
368 loadTimeData.getString('danger_restore'));
369 this.malwareNodeControls_.appendChild(this.malwareSave_);
370 this.malwareDiscard_ = createLink(
371 this.discardDangerous_.bind(this),
372 loadTimeData.getString('control_removefromlist'));
373 this.malwareNodeControls_.appendChild(this.malwareDiscard_);
374 this.danger_.appendChild(this.malwareNodeControls_);
376 // Buttons for the dangerous but not malicious case.
377 this.dangerSave_ = createButton(
378 this.saveDangerous_.bind(this),
379 loadTimeData.getString('danger_save'));
380 this.danger_.appendChild(this.dangerSave_);
382 this.dangerDiscard_ = createButton(
383 this.discardDangerous_.bind(this),
384 loadTimeData.getString('danger_discard'));
385 this.danger_.appendChild(this.dangerDiscard_);
387 // Update member vars.
388 this.update(download);
392 * The states a download can be in. These correspond to states defined in
393 * DownloadsDOMHandler::CreateDownloadItemValue
396 IN_PROGRESS: 'IN_PROGRESS',
397 CANCELLED: 'CANCELLED',
398 COMPLETE: 'COMPLETE',
400 DANGEROUS: 'DANGEROUS',
401 INTERRUPTED: 'INTERRUPTED',
405 * Explains why a download is in DANGEROUS state.
407 Download.DangerType = {
408 NOT_DANGEROUS: 'NOT_DANGEROUS',
409 DANGEROUS_FILE: 'DANGEROUS_FILE',
410 DANGEROUS_URL: 'DANGEROUS_URL',
411 DANGEROUS_CONTENT: 'DANGEROUS_CONTENT',
412 UNCOMMON_CONTENT: 'UNCOMMON_CONTENT',
413 DANGEROUS_HOST: 'DANGEROUS_HOST',
414 POTENTIALLY_UNWANTED: 'POTENTIALLY_UNWANTED',
418 * Constants for the progress meter.
421 Download.Progress = (function() {
422 var scale = window.devicePixelRatio;
429 base: -0.5 * Math.PI,
435 * Updates the download to reflect new data.
436 * @param {Object} download A backend download object (see downloads_ui.cc)
438 Download.prototype.update = function(download) {
439 this.id_ = download.id;
440 this.filePath_ = download.file_path;
441 this.fileUrl_ = download.file_url;
442 this.fileName_ = download.file_name;
443 this.url_ = download.url;
444 this.state_ = download.state;
445 this.fileExternallyRemoved_ = download.file_externally_removed;
446 this.dangerType_ = download.danger_type;
447 this.lastReasonDescription_ = download.last_reason_text;
448 this.byExtensionId_ = download.by_ext_id;
449 this.byExtensionName_ = download.by_ext_name;
451 this.since_ = download.since_string;
452 this.date_ = download.date_string;
454 // See DownloadItem::PercentComplete
455 this.percent_ = Math.max(download.percent, 0);
456 this.progressStatusText_ = download.progress_status_text;
457 this.received_ = download.received;
459 if (this.state_ == Download.States.DANGEROUS) {
460 this.updateDangerousFile();
462 downloads.scheduleIconLoad(this.nodeImg_,
463 'chrome://fileicon/' +
464 encodeURIComponent(this.filePath_) +
465 '?scale=' + window.devicePixelRatio + 'x');
467 if (this.state_ == Download.States.COMPLETE &&
468 !this.fileExternallyRemoved_) {
469 this.nodeFileLink_.textContent = this.fileName_;
470 this.nodeFileLink_.href = this.fileUrl_;
471 this.nodeFileLink_.oncontextmenu = null;
472 } else if (this.nodeFileName_.textContent != this.fileName_) {
473 this.nodeFileName_.textContent = this.fileName_;
475 if (this.state_ == Download.States.INTERRUPTED)
476 this.nodeFileName_.classList.add('interrupted');
478 showInline(this.nodeFileLink_,
479 this.state_ == Download.States.COMPLETE &&
480 !this.fileExternallyRemoved_);
481 // nodeFileName_ has to be inline-block to avoid the 'interaction' with
482 // nodeStatus_. If both are inline, it appears that their text contents
483 // are merged before the bidi algorithm is applied leading to an
484 // undesirable reordering. http://crbug.com/13216
485 showInlineBlock(this.nodeFileName_,
486 this.state_ != Download.States.COMPLETE ||
487 this.fileExternallyRemoved_);
489 if (this.state_ == Download.States.IN_PROGRESS) {
490 this.nodeProgressForeground_.style.display = 'block';
491 this.nodeProgressBackground_.style.display = 'block';
493 // Draw a pie-slice for the progress.
494 this.canvasProgress_.globalCompositeOperation = 'copy';
495 this.canvasProgress_.drawImage(this.canvasProgressForegroundImage_, 0, 0);
496 this.canvasProgress_.globalCompositeOperation = 'destination-in';
497 this.canvasProgress_.beginPath();
498 this.canvasProgress_.moveTo(Download.Progress.centerX,
499 Download.Progress.centerY);
501 // Draw an arc CW for both RTL and LTR. http://crbug.com/13215
502 this.canvasProgress_.arc(Download.Progress.centerX,
503 Download.Progress.centerY,
504 Download.Progress.radius,
505 Download.Progress.base,
506 Download.Progress.base + Math.PI * 0.02 *
507 Number(this.percent_),
510 this.canvasProgress_.lineTo(Download.Progress.centerX,
511 Download.Progress.centerY);
512 this.canvasProgress_.fill();
513 this.canvasProgress_.closePath();
514 } else if (this.nodeProgressBackground_) {
515 this.nodeProgressForeground_.style.display = 'none';
516 this.nodeProgressBackground_.style.display = 'none';
519 if (this.controlShow_) {
520 showInline(this.controlShow_,
521 this.state_ == Download.States.COMPLETE &&
522 !this.fileExternallyRemoved_);
524 showInline(this.controlRetry_, download.retry);
525 this.controlRetry_.href = this.url_;
526 showInline(this.controlPause_, this.state_ == Download.States.IN_PROGRESS);
527 showInline(this.controlResume_, download.resume);
528 var showCancel = this.state_ == Download.States.IN_PROGRESS ||
529 this.state_ == Download.States.PAUSED;
530 showInline(this.controlCancel_, showCancel);
531 showInline(this.controlRemove_, !showCancel);
533 if (this.byExtensionId_ && this.byExtensionName_) {
534 // Format 'control_by_extension' with a link instead of plain text by
535 // splitting the formatted string into pieces.
537 var formatted = loadTimeData.getStringF('control_by_extension', slug);
538 var slugIndex = formatted.indexOf(slug);
539 this.controlByExtension_.textContent = formatted.substr(0, slugIndex);
540 this.controlByExtensionLink_ = document.createElement('a');
541 this.controlByExtensionLink_.href =
542 'chrome://extensions#' + this.byExtensionId_;
543 this.controlByExtensionLink_.textContent = this.byExtensionName_;
544 this.controlByExtension_.appendChild(this.controlByExtensionLink_);
545 if (slugIndex < (formatted.length - slug.length))
546 this.controlByExtension_.appendChild(document.createTextNode(
547 formatted.substr(slugIndex + 1)));
550 this.nodeSince_.textContent = this.since_;
551 this.nodeDate_.textContent = this.date_;
552 // Don't unnecessarily update the url, as doing so will remove any
553 // text selection the user has started (http://crbug.com/44982).
554 if (this.nodeURL_.textContent != this.url_) {
555 this.nodeURL_.textContent = this.url_;
556 this.nodeURL_.href = this.url_;
558 this.nodeStatus_.textContent = this.getStatusText_();
560 this.danger_.style.display = 'none';
561 this.safe_.style.display = 'block';
566 * Decorates the icons, strings, and buttons for a download to reflect the
567 * danger level of a file. Dangerous & malicious files are treated differently.
569 Download.prototype.updateDangerousFile = function() {
570 switch (this.dangerType_) {
571 case Download.DangerType.DANGEROUS_FILE: {
572 this.dangerDesc_.textContent = loadTimeData.getStringF(
573 'danger_file_desc', this.fileName_);
576 case Download.DangerType.DANGEROUS_URL: {
577 this.dangerDesc_.textContent = loadTimeData.getString('danger_url_desc');
580 case Download.DangerType.DANGEROUS_CONTENT: // Fall through.
581 case Download.DangerType.DANGEROUS_HOST: {
582 this.dangerDesc_.textContent = loadTimeData.getStringF(
583 'danger_content_desc', this.fileName_);
586 case Download.DangerType.UNCOMMON_CONTENT: {
587 this.dangerDesc_.textContent = loadTimeData.getStringF(
588 'danger_uncommon_desc', this.fileName_);
591 case Download.DangerType.POTENTIALLY_UNWANTED: {
592 this.dangerDesc_.textContent = loadTimeData.getStringF(
593 'danger_settings_desc', this.fileName_);
598 if (this.dangerType_ == Download.DangerType.DANGEROUS_FILE) {
599 downloads.scheduleIconLoad(
601 'chrome://theme/IDR_WARNING?scale=' + window.devicePixelRatio + 'x');
603 downloads.scheduleIconLoad(
605 'chrome://theme/IDR_SAFEBROWSING_WARNING?scale=' +
606 window.devicePixelRatio + 'x');
607 this.dangerDesc_.className = 'malware-description';
610 if (this.dangerType_ == Download.DangerType.DANGEROUS_CONTENT ||
611 this.dangerType_ == Download.DangerType.DANGEROUS_HOST ||
612 this.dangerType_ == Download.DangerType.DANGEROUS_URL ||
613 this.dangerType_ == Download.DangerType.POTENTIALLY_UNWANTED) {
614 this.malwareNodeControls_.style.display = 'block';
615 this.dangerDiscard_.style.display = 'none';
616 this.dangerSave_.style.display = 'none';
618 this.malwareNodeControls_.style.display = 'none';
619 this.dangerDiscard_.style.display = 'inline';
620 this.dangerSave_.style.display = 'inline';
623 this.danger_.style.display = 'block';
624 this.safe_.style.display = 'none';
628 * Removes applicable bits from the DOM in preparation for deletion.
630 Download.prototype.clear = function() {
631 this.safe_.ondragstart = null;
632 this.nodeFileLink_.onclick = null;
633 if (this.controlShow_) {
634 this.controlShow_.onclick = null;
636 this.controlCancel_.onclick = null;
637 this.controlPause_.onclick = null;
638 this.controlResume_.onclick = null;
639 this.dangerDiscard_.onclick = null;
640 this.dangerSave_.onclick = null;
641 this.malwareDiscard_.onclick = null;
642 this.malwareSave_.onclick = null;
644 this.node.innerHTML = '';
649 * @return {string} User-visible status update text.
651 Download.prototype.getStatusText_ = function() {
652 switch (this.state_) {
653 case Download.States.IN_PROGRESS:
654 return this.progressStatusText_;
655 case Download.States.CANCELLED:
656 return loadTimeData.getString('status_cancelled');
657 case Download.States.PAUSED:
658 return loadTimeData.getString('status_paused');
659 case Download.States.DANGEROUS:
660 // danger_url_desc is also used by DANGEROUS_CONTENT.
661 var desc = this.dangerType_ == Download.DangerType.DANGEROUS_FILE ?
662 'danger_file_desc' : 'danger_url_desc';
663 return loadTimeData.getString(desc);
664 case Download.States.INTERRUPTED:
665 return this.lastReasonDescription_;
666 case Download.States.COMPLETE:
667 return this.fileExternallyRemoved_ ?
668 loadTimeData.getString('status_removed') : '';
673 * Tells the backend to initiate a drag, allowing users to drag
674 * files from the download page and have them appear as native file
676 * @return {boolean} Returns false to prevent the default action.
679 Download.prototype.drag_ = function() {
680 chrome.send('drag', [this.id_.toString()]);
685 * Tells the backend to open this file.
686 * @return {boolean} Returns false to prevent the default action.
689 Download.prototype.openFile_ = function() {
690 chrome.send('openFile', [this.id_.toString()]);
695 * Tells the backend that the user chose to save a dangerous file.
696 * @return {boolean} Returns false to prevent the default action.
699 Download.prototype.saveDangerous_ = function() {
700 chrome.send('saveDangerous', [this.id_.toString()]);
705 * Tells the backend that the user chose to discard a dangerous file.
706 * @return {boolean} Returns false to prevent the default action.
709 Download.prototype.discardDangerous_ = function() {
710 chrome.send('discardDangerous', [this.id_.toString()]);
711 downloads.remove(this.id_);
716 * Tells the backend to show the file in explorer.
717 * @return {boolean} Returns false to prevent the default action.
720 Download.prototype.show_ = function() {
721 chrome.send('show', [this.id_.toString()]);
726 * Tells the backend to pause this download.
727 * @return {boolean} Returns false to prevent the default action.
730 Download.prototype.pause_ = function() {
731 chrome.send('pause', [this.id_.toString()]);
736 * Tells the backend to resume this download.
737 * @return {boolean} Returns false to prevent the default action.
740 Download.prototype.resume_ = function() {
741 chrome.send('resume', [this.id_.toString()]);
746 * Tells the backend to remove this download from history and download shelf.
747 * @return {boolean} Returns false to prevent the default action.
750 Download.prototype.remove_ = function() {
751 if (loadTimeData.getBoolean('allow_deleting_history')) {
752 chrome.send('remove', [this.id_.toString()]);
758 * Tells the backend to cancel this download.
759 * @return {boolean} Returns false to prevent the default action.
762 Download.prototype.cancel_ = function() {
763 chrome.send('cancel', [this.id_.toString()]);
767 ///////////////////////////////////////////////////////////////////////////////
769 var downloads, resultsTimeout;
771 // TODO(benjhayden): Rename Downloads to DownloadManager, downloads to
772 // downloadManager or theDownloadManager or DownloadManager.get() to prevent
773 // confusing Downloads with Download.
776 * The FIFO array that stores updates of download files to be appeared
777 * on the download page. It is guaranteed that the updates in this array
778 * are reflected to the download page in a FIFO order.
783 chrome.send('onPageLoaded');
785 downloads = new Downloads();
789 var clearAllHolder = $('clear-all-holder');
791 if (loadTimeData.getBoolean('allow_deleting_history')) {
792 clearAllElement = createLink(clearAll, loadTimeData.getString('clear_all'));
793 clearAllElement.classList.add('clear-all-link');
794 clearAllHolder.classList.remove('disabled-link');
796 clearAllElement = document.createTextNode(
797 loadTimeData.getString('clear_all'));
798 clearAllHolder.classList.add('disabled-link');
800 if (!loadTimeData.getBoolean('show_delete_history'))
801 clearAllHolder.hidden = true;
803 clearAllHolder.appendChild(clearAllElement);
804 clearAllElement.oncontextmenu = function() { return false; };
806 // TODO(jhawkins): Use a link-button here.
807 var openDownloadsFolderLink = $('open-downloads-folder');
808 openDownloadsFolderLink.onclick = function() {
809 chrome.send('openDownloadsFolder');
811 openDownloadsFolderLink.oncontextmenu = function() { return false; };
813 $('search-link').onclick = function(e) {
816 $('term').value = '';
820 $('term').onsearch = function(e) {
821 setSearch(this.value);
825 function setSearch(searchText) {
826 fifoResults.length = 0;
827 downloads.setSearchText(searchText);
828 searchText = searchText.toString().match(/(?:[^\s"]+|"[^"]*")+/g);
830 searchText = searchText.map(function(term) {
832 return (term.match(/\s/) &&
833 term[0].match(/["']/) &&
834 term[term.length - 1] == term[0]) ?
835 term.substr(1, term.length - 2) : term;
840 chrome.send('getDownloads', searchText);
843 function clearAll() {
844 if (!loadTimeData.getBoolean('allow_deleting_history'))
847 fifoResults.length = 0;
849 downloads.setSearchText('');
850 chrome.send('clearAll');
853 ///////////////////////////////////////////////////////////////////////////////
856 * Our history system calls this function with results from searches or when
857 * downloads are added or removed.
858 * @param {Array.<Object>} results List of updates.
860 function downloadsList(results) {
861 if (downloads && downloads.isUpdateNeeded(results)) {
863 clearTimeout(resultsTimeout);
864 fifoResults.length = 0;
866 downloadUpdated(results);
868 downloads.updateSummary();
872 * When a download is updated (progress, state change), this is called.
873 * @param {Array.<Object>} results List of updates for the download process.
875 function downloadUpdated(results) {
876 // Sometimes this can get called too early.
880 fifoResults = fifoResults.concat(results);
881 tryDownloadUpdatedPeriodically();
885 * Try to reflect as much updates as possible within 50ms.
886 * This function is scheduled again and again until all updates are reflected.
888 function tryDownloadUpdatedPeriodically() {
889 var start = Date.now();
890 while (fifoResults.length) {
891 var result = fifoResults.shift();
892 downloads.updated(result);
893 // Do as much as we can in 50ms.
894 if (Date.now() - start > 50) {
895 clearTimeout(resultsTimeout);
896 resultsTimeout = setTimeout(tryDownloadUpdatedPeriodically, 5);
902 // Add handlers to HTML elements.
903 window.addEventListener('DOMContentLoaded', load);