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