Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(294)

Side by Side Diff: chrome/browser/resources/downloads.js

Issue 9689007: Downloads: First pass at a cleanup to match webdev style. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: One more. Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 };
OLDNEW
« no previous file with comments | « chrome/browser/resources/downloads.html ('k') | chrome/browser/resources/downloads/downloads.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698