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