- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / examples / api / downloads / download_manager / popup.js
1 // Copyright (c) 2013 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 function pointInElement(p, elem) {
6   return ((p.x >= elem.offsetLeft) &&
7           (p.x <= (elem.offsetLeft + elem.offsetWidth)) &&
8           (p.y >= elem.offsetTop) &&
9           (p.y <= (elem.offsetTop + elem.offsetHeight)));
10 };
11
12 function setLastOpened() {
13   localStorage.popupLastOpened = (new Date()).getTime();
14   chrome.runtime.sendMessage('poll');
15 };
16
17 function loadI18nMessages() {
18   function setProperty(selector, prop, msg) {
19     document.querySelector(selector)[prop] = chrome.i18n.getMessage(msg);
20   }
21
22   setProperty('title', 'innerText', 'tabTitle');
23   setProperty('#q', 'placeholder', 'searchPlaceholder');
24   setProperty('#clear-all', 'title', 'clearAllTitle');
25   setProperty('#open-folder', 'title', 'openDownloadsFolderTitle');
26   setProperty('#empty', 'innerText', 'zeroItems');
27   setProperty('#searching', 'innerText', 'searching');
28   setProperty('#search-zero', 'innerText', 'zeroSearchResults');
29   setProperty('#management-permission-info', 'innerText',
30               'managementPermissionInfo');
31   setProperty('#grant-management-permission', 'innerText',
32               'grantManagementPermission');
33   setProperty('#older', 'innerText', 'showOlderDownloads');
34   setProperty('#loading-older', 'innerText', 'loadingOlderDownloads');
35   setProperty('.pause', 'title', 'pauseTitle');
36   setProperty('.resume', 'title', 'resumeTitle');
37   setProperty('.cancel', 'title', 'cancelTitle');
38   setProperty('.show-folder', 'title', 'showInFolderTitle');
39   setProperty('.erase', 'title', 'eraseTitle');
40   setProperty('.url', 'title', 'retryTitle');
41   setProperty('.referrer', 'title', 'referrerTitle');
42   setProperty('.open-filename', 'title', 'openTitle');
43   setProperty('#bad-chrome-version', 'innerText', 'badChromeVersion');
44   setProperty('.remove-file', 'title', 'removeFileTitle');
45
46   document.querySelector('.progress').style.minWidth =
47     getTextWidth(formatBytes(1024 * 1024 * 1023.9) + '/' +
48                  formatBytes(1024 * 1024 * 1023.9)) + 'px';
49
50   // This only covers {timeLeft,openWhenComplete}{Finishing,Days}. If
51   // ...Hours/Minutes/Seconds could be longer for any locale, then this should
52   // test them.
53   var max_time_left_width = 0;
54   for (var i = 0; i < 4; ++i) {
55     max_time_left_width = Math.max(max_time_left_width, getTextWidth(
56         formatTimeLeft(0 == (i % 2),
57                        (i < 2) ? 0 : ((100 * 24) + 23) * 60 * 60 * 1000)));
58   }
59   document.querySelector('body div.item span.time-left').style.minWidth =
60      max_time_left_width + 'px';
61 };
62
63 function getTextWidth(s) {
64   var probe = document.getElementById('text-width-probe');
65   probe.innerText = s;
66   return probe.offsetWidth;
67 };
68
69 function formatDateTime(date) {
70   var now = new Date();
71   var zpad_mins = ':' + (date.getMinutes() < 10 ? '0' : '') + date.getMinutes();
72   if (date.getYear() != now.getYear()) {
73     return '' + (1900 + date.getYear());
74   } else if ((date.getMonth() != now.getMonth()) ||
75              (date.getDate() != now.getDate())) {
76     return date.getDate() + ' ' + chrome.i18n.getMessage(
77       'month' + date.getMonth() + 'abbr');
78   } else if (date.getHours() == 12) {
79     return '12' + zpad_mins + 'pm';
80   } else if (date.getHours() > 12) {
81     return (date.getHours() - 12) + zpad_mins + 'pm';
82   }
83   return date.getHours() + zpad_mins + 'am';
84 }
85
86 function formatBytes(n) {
87   if (n < 1024) {
88     return n + 'B';
89   }
90   var prefixes = 'KMGTPEZY';
91   var mul = 1024;
92   for (var i = 0; i < prefixes.length; ++i) {
93     if (n < (1024 * mul)) {
94       return (parseInt(n / mul) + '.' + parseInt(10 * ((n / mul) % 1)) +
95               prefixes[i] + 'B');
96     }
97     mul *= 1024;
98   }
99   return '!!!';
100 }
101
102 function formatTimeLeft(openWhenComplete, ms) {
103   var prefix = openWhenComplete ? 'openWhenComplete' : 'timeLeft';
104   if (ms < 1000) {
105     return chrome.i18n.getMessage(prefix + 'Finishing');
106   }
107   var days = parseInt(ms / (24 * 60 * 60 * 1000));
108   var hours = parseInt(ms / (60 * 60 * 1000)) % 24;
109   if (days) {
110     return chrome.i18n.getMessage(prefix + 'Days', [days, hours]);
111   }
112   var minutes = parseInt(ms / (60 * 1000)) % 60;
113   if (hours) {
114     return chrome.i18n.getMessage(prefix + 'Hours', [hours, minutes]);
115   }
116   var seconds = parseInt(ms / 1000) % 60;
117   if (minutes) {
118     return chrome.i18n.getMessage(prefix + 'Minutes', [minutes, seconds]);
119   }
120   return chrome.i18n.getMessage(prefix + 'Seconds', [seconds]);
121 }
122
123 function ratchetWidth(w) {
124   var current = parseInt(document.body.style.minWidth) || 0;
125   document.body.style.minWidth = Math.max(w, current) + 'px';
126 }
127
128 function ratchetHeight(h) {
129   var current = parseInt(document.body.style.minHeight) || 0;
130   document.body.style.minHeight = Math.max(h, current) + 'px';
131 }
132
133 function binarySearch(array, target, cmp) {
134   var low = 0, high = array.length - 1, i, comparison;
135   while (low <= high) {
136     i = (low + high) >> 1;
137     comparison = cmp(target, array[i]);
138     if (comparison < 0) {
139       low = i + 1;
140     } else if (comparison > 0) {
141       high = i - 1;
142     } else {
143       return i;
144     }
145   }
146   return i;
147 };
148
149 function arrayFrom(seq) {
150   return Array.prototype.slice.apply(seq);
151 };
152
153 function DownloadItem(data) {
154   var item = this;
155   for (var prop in data) {
156     item[prop] = data[prop];
157   }
158   item.startTime = new Date(item.startTime);
159   if (item.canResume == undefined) {
160     DownloadItem.canResumeHack = true;
161   }
162
163   item.div = document.querySelector('body>div.item').cloneNode(true);
164   item.div.id = 'item' + item.id;
165   item.div.item = item;
166
167   var items_div = document.getElementById('items');
168   if ((items_div.childNodes.length == 0) ||
169       (item.startTime.getTime() < items_div.childNodes[
170        items_div.childNodes.length - 1].item.startTime.getTime())) {
171     items_div.appendChild(item.div);
172   } else if (item.startTime.getTime() >
173              items_div.childNodes[0].item.startTime.getTime()) {
174     items_div.insertBefore(item.div, items_div.childNodes[0]);
175   } else {
176     var adjacent_div = items_div.childNodes[
177       binarySearch(arrayFrom(items_div.childNodes),
178                    item.startTime.getTime(),
179                    function(target, other) {
180           return target - other.item.startTime.getTime();
181     })];
182     var adjacent_item = adjacent_div.item;
183     if (adjacent_item.startTime.getTime() < item.startTime.getTime()) {
184       items_div.insertBefore(item.div, adjacent_div);
185     } else {
186       items_div.insertBefore(item.div, adjacent_div.nextSibling);
187     }
188   }
189
190   item.getElement('referrer').onclick = function() {
191     chrome.tabs.create({url: item.referrer});
192     return false;
193   };
194   item.getElement('by-ext').onclick = function() {
195     chrome.tabs.create({url: 'chrome://extensions#' + item.byExtensionId});
196     return false;
197   }
198   item.getElement('open-filename').onclick = function() {
199     item.open();
200     return false;
201   };
202   item.getElement('open-filename').ondragstart = function() {
203     item.drag();
204     return false;
205   };
206   item.getElement('pause').onclick = function() {
207     item.pause();
208     return false;
209   };
210   item.getElement('cancel').onclick = function() {
211     item.cancel();
212     return false;
213   };
214   item.getElement('resume').onclick = function() {
215     item.resume();
216     return false;
217   };
218   item.getElement('show-folder').onclick = function() {
219     item.show();
220     return false;
221   };
222   item.getElement('remove-file').onclick = function() {
223     item.removeFile();
224     return false;
225   };
226   item.getElement('erase').onclick = function() {
227     item.erase();
228     return false;
229   };
230
231   item.more_mousemove = function(evt) {
232     var mouse = {x:evt.x, y:evt.y+document.body.scrollTop};
233     if (item.getElement('more') &&
234         (pointInElement(mouse, item.div) ||
235          pointInElement(mouse, item.getElement('more')))) {
236       return;
237     }
238     if (item.getElement('more')) {
239       item.getElement('more').hidden = true;
240     }
241     window.removeEventListener('mousemove', item.more_mousemove);
242   };
243   [item.div, item.getElement('more')].concat(
244       item.getElement('more').children).forEach(function(elem) {
245     elem.onmouseover = function() {
246       arrayFrom(items_div.children).forEach(function(other) {
247         if (other.item != item) {
248           other.item.getElement('more').hidden = true;
249         }
250       });
251       item.getElement('more').hidden = false;
252       item.getElement('more').style.top =
253         (item.div.offsetTop + item.div.offsetHeight) + 'px';
254       item.getElement('more').style.left = item.div.offsetLeft + 'px';
255       if (window.innerHeight < (parseInt(item.getElement('more').style.top) +
256                                 item.getElement('more').offsetHeight)) {
257         item.getElement('more').style.top = (
258           item.div.offsetTop - item.getElement('more').offsetHeight) + 'px';
259       }
260       window.addEventListener('mousemove', item.more_mousemove);
261     };
262   });
263
264   if (item.referrer) {
265     item.getElement('referrer').href = item.referrer;
266   } else {
267     item.getElement('referrer').hidden = true;
268   }
269   item.getElement('url').href = item.url;
270   item.getElement('url').innerText = item.url;
271   item.render();
272 }
273 DownloadItem.canResumeHack = false;
274
275 DownloadItem.prototype.getElement = function(name) {
276   return document.querySelector('#item' + this.id + ' .' + name);
277 };
278
279 DownloadItem.prototype.render = function() {
280   var item = this;
281   var now = new Date();
282   var in_progress = (item.state == 'in_progress')
283   var openable = (item.state != 'interrupted') && item.exists && !item.deleted;
284
285   item.startTime = new Date(item.startTime);
286   if (DownloadItem.canResumeHack) {
287     item.canResume = in_progress && item.paused;
288   }
289   if (item.filename) {
290     item.basename = item.filename.substring(Math.max(
291       item.filename.lastIndexOf('\\'),
292       item.filename.lastIndexOf('/')) + 1);
293   }
294   if (item.estimatedEndTime) {
295     item.estimatedEndTime = new Date(item.estimatedEndTime);
296   }
297   if (item.endTime) {
298     item.endTime = new Date(item.endTime);
299   }
300
301   if (item.filename && !item.icon_url) {
302     chrome.downloads.getFileIcon(
303       item.id,
304       {'size': 32},
305       function(icon_url) {
306         item.getElement('icon').hidden = !icon_url;
307         if (icon_url) {
308           item.icon_url = icon_url;
309           item.getElement('icon').src = icon_url;
310         }
311     });
312   }
313
314   item.getElement('removed').style.display = openable ? 'none' : 'inline';
315   item.getElement('open-filename').style.display = (
316     openable ? 'inline' : 'none');
317   item.getElement('in-progress').hidden = !in_progress;
318   item.getElement('pause').style.display = (
319     !in_progress || item.paused) ? 'none' : 'inline-block';
320   item.getElement('resume').style.display = (
321     !in_progress || !item.canResume) ? 'none' : 'inline-block';
322   item.getElement('cancel').style.display = (
323     !in_progress ? 'none' : 'inline-block');
324   item.getElement('remove-file').hidden = (
325     (item.state != 'complete') ||
326     !item.exists ||
327     item.deleted ||
328     !chrome.downloads.removeFile);
329   item.getElement('erase').hidden = in_progress;
330
331   var could_progress = in_progress || item.canResume;
332   item.getElement('progress').style.display = (
333     could_progress ? 'inline-block' : 'none');
334   item.getElement('meter').hidden = !could_progress || !item.totalBytes;
335
336   item.getElement('removed').innerText = item.basename;
337   item.getElement('open-filename').innerText = item.basename;
338
339   function setByExtension(show) {
340     if (show) {
341       item.getElement('by-ext').title = item.byExtensionName;
342       item.getElement('by-ext').href =
343         'chrome://extensions#' + item.byExtensionId;
344       item.getElement('by-ext img').src =
345         'chrome://extension-icon/' + item.byExtensionId + '/48/1';
346     } else {
347       item.getElement('by-ext').hidden = true;
348     }
349   }
350   if (item.byExtensionId && item.byExtensionName) {
351     chrome.permissions.contains({permissions: ['management']},
352                                 function(result) {
353       if (result) {
354         setByExtension(true);
355       } else {
356         setByExtension(false);
357         if (!localStorage.managementPermissionDenied) {
358           document.getElementById('request-management-permission').hidden =
359             false;
360           document.getElementById('grant-management-permission').onclick =
361               function() {
362             chrome.permissions.request({permissions: ['management']},
363                                       function(granted) {
364               setByExtension(granted);
365               if (!granted) {
366                 localStorage.managementPermissionDenied = true;
367               }
368             });
369             return false;
370           };
371         }
372       }
373     });
374   } else {
375     setByExtension(false);
376   }
377
378   if (!item.getElement('error').hidden) {
379     if (item.error) {
380       // TODO(benjhayden) When https://codereview.chromium.org/16924017/ is
381       // released, set minimum_chrome_version and remove the error_N messages.
382       item.getElement('error').innerText = chrome.i18n.getMessage(
383           'error_' + item.error);
384       if (!item.getElement('error').innerText) {
385         item.getElement('error').innerText = item.error;
386       }
387     } else if (!openable) {
388       item.getElement('error').innerText = chrome.i18n.getMessage(
389           'errorRemoved');
390     }
391   }
392
393   item.getElement('complete-size').innerText = formatBytes(
394     item.bytesReceived);
395   if (item.totalBytes && (item.state != 'complete')) {
396     item.getElement('progress').innerText = (
397       item.getElement('complete-size').innerText + '/' +
398       formatBytes(item.totalBytes));
399     item.getElement('meter').children[0].style.width = parseInt(
400         100 * item.bytesReceived / item.totalBytes) + '%';
401   }
402
403   if (in_progress) {
404     if (item.estimatedEndTime && !item.paused) {
405       var openWhenComplete = false;
406       try {
407         openWhenComplete = JSON.parse(localStorage.openWhenComplete).indexOf(
408             item.id) >= 0;
409       } catch (e) {
410       }
411       item.getElement('time-left').innerText = formatTimeLeft(
412           openWhenComplete, item.estimatedEndTime.getTime() - now.getTime());
413     } else {
414       item.getElement('time-left').innerText = String.fromCharCode(160);
415     }
416   }
417
418   if (item.startTime) {
419     item.getElement('start-time').innerText = formatDateTime(
420         item.startTime);
421   }
422
423   ratchetWidth(item.getElement('icon').offsetWidth +
424                item.getElement('file-url').offsetWidth +
425                item.getElement('cancel').offsetWidth +
426                item.getElement('pause').offsetWidth +
427                item.getElement('resume').offsetWidth);
428   ratchetWidth(item.getElement('more').offsetWidth);
429
430   this.maybeAccept();
431 };
432
433 DownloadItem.prototype.onChanged = function(delta) {
434   for (var key in delta) {
435     if (key != 'id') {
436       this[key] = delta[key].current;
437     }
438   }
439   this.render();
440   if (delta.state) {
441     setLastOpened();
442   }
443   if ((this.state == 'in_progress') && !this.paused) {
444     DownloadManager.startPollingProgress();
445   }
446 };
447
448 DownloadItem.prototype.onErased = function() {
449   window.removeEventListener('mousemove', this.more_mousemove);
450   document.getElementById('items').removeChild(this.div);
451 };
452
453 DownloadItem.prototype.drag = function() {
454   chrome.downloads.drag(this.id);
455 };
456
457 DownloadItem.prototype.show = function() {
458   chrome.downloads.show(this.id);
459 };
460
461 DownloadItem.prototype.open = function() {
462   if (this.state == 'complete') {
463     chrome.downloads.open(this.id);
464     return;
465   }
466   chrome.runtime.sendMessage({openWhenComplete:this.id});
467 };
468
469 DownloadItem.prototype.removeFile = function() {
470   chrome.downloads.removeFile(this.id);
471   this.deleted = true;
472   this.render();
473 };
474
475 DownloadItem.prototype.erase = function() {
476   chrome.downloads.erase({id: this.id});
477 };
478
479 DownloadItem.prototype.pause = function() {
480   chrome.downloads.pause(this.id);
481 };
482
483 DownloadItem.prototype.resume = function() {
484   chrome.downloads.resume(this.id);
485 };
486
487 DownloadItem.prototype.cancel = function() {
488   chrome.downloads.cancel(this.id);
489 };
490
491 DownloadItem.prototype.maybeAccept = function() {
492   // This function is safe to call at any time for any item, and it will always
493   // do the right thing, which is to display the danger prompt only if the item
494   // is in_progress and dangerous, and if the prompt is not already displayed.
495   if ((this.state != 'in_progress') ||
496       (this.danger == 'safe') ||
497       (this.danger == 'accepted') ||
498       DownloadItem.prototype.maybeAccept.accepting_danger) {
499     return;
500   }
501   ratchetWidth(400);
502   ratchetHeight(200);
503   DownloadItem.prototype.maybeAccept.accepting_danger = true;
504   // On Mac, window.onload is run while the popup is animating in, before it is
505   // considered "visible". Prompts will not be displayed over an invisible
506   // window, so the popup will become stuck. Just wait a little bit for the
507   // window to finish animating in. http://crbug.com/280107
508   // This has been fixed, so this setTimeout can be removed when the fix has
509   // been released to stable, and minimum_chrome_version can be set.
510   var id = this.id;
511   setTimeout(function() {
512     chrome.downloads.acceptDanger(id, function() {
513       DownloadItem.prototype.maybeAccept.accepting_danger = false;
514       arrayFrom(document.getElementById('items').childNodes).forEach(
515         function(item_div) { item_div.item.maybeAccept(); });
516     });
517   }, 500);
518 };
519 DownloadItem.prototype.maybeAccept.accepting_danger = false;
520
521 var DownloadManager = {};
522
523 DownloadManager.showingOlder = false;
524
525 DownloadManager.getItem = function(id) {
526   var item_div = document.getElementById('item' + id);
527   return item_div ? item_div.item : null;
528 };
529
530 DownloadManager.getOrCreate = function(data) {
531   var item = DownloadManager.getItem(data.id);
532   return item ? item : new DownloadItem(data);
533 };
534
535 DownloadManager.forEachItem = function(cb) {
536   // Calls cb(item, index) in the order that they are displayed, i.e. in order
537   // of decreasing startTime.
538   arrayFrom(document.getElementById('items').childNodes).forEach(
539     function(item_div, index) { cb(item_div.item, index); });
540 };
541
542 DownloadManager.startPollingProgress = function() {
543   if (DownloadManager.startPollingProgress.tid < 0) {
544     DownloadManager.startPollingProgress.tid = setTimeout(
545       DownloadManager.startPollingProgress.pollProgress,
546       DownloadManager.startPollingProgress.MS);
547   }
548 }
549 DownloadManager.startPollingProgress.MS = 200;
550 DownloadManager.startPollingProgress.tid = -1;
551 DownloadManager.startPollingProgress.pollProgress = function() {
552   DownloadManager.startPollingProgress.tid = -1;
553   chrome.downloads.search({state: 'in_progress', paused: false},
554       function(results) {
555     if (!results.length)
556       return;
557     results.forEach(function(result) {
558       var item = DownloadManager.getOrCreate(result);
559       for (var prop in result) {
560         item[prop] = result[prop];
561       }
562       item.render();
563       if ((item.state == 'in_progress') && !item.paused) {
564         DownloadManager.startPollingProgress();
565       }
566     });
567   });
568 };
569
570 DownloadManager.showNew = function() {
571   var any_items = (document.getElementById('items').childNodes.length > 0);
572   document.getElementById('empty').style.display =
573     any_items ? 'none' : 'inline-block';
574   document.getElementById('head').style.borderBottomWidth =
575     (any_items ? 1 : 0) + 'px';
576   document.getElementById('clear-all').hidden = !any_items;
577
578   var query_search = document.getElementById('q');
579   query_search.hidden = !any_items;
580
581   if (!any_items) {
582     return;
583   }
584   var old_ms = (new Date()).getTime() - kOldMs;
585   var any_hidden = false;
586   var any_showing = false;
587   // First show up to kShowNewMax items newer than kOldMs. If there aren't any
588   // items newer than kOldMs, then show up to kShowNewMax items of any age. If
589   // there are any hidden items, show the Show Older button.
590   DownloadManager.forEachItem(function(item, index) {
591     item.div.hidden = !DownloadManager.showingOlder && (
592       (item.startTime.getTime() < old_ms) || (index >= kShowNewMax));
593     any_hidden = any_hidden || item.div.hidden;
594     any_showing = any_showing || !item.div.hidden;
595   });
596   if (!any_showing) {
597     any_hidden = false;
598     DownloadManager.forEachItem(function(item, index) {
599       item.div.hidden = !DownloadManager.showingOlder && (index >= kShowNewMax);
600       any_hidden = any_hidden || item.div.hidden;
601       any_showing = any_showing || !item.div.hidden;
602     });
603   }
604   document.getElementById('older').hidden = !any_hidden;
605
606   query_search.focus();
607 };
608
609 DownloadManager.showOlder = function() {
610   DownloadManager.showingOlder = true;
611   var loading_older_span = document.getElementById('loading-older');
612   document.getElementById('older').hidden = true;
613   loading_older_span.hidden = false;
614   chrome.downloads.search({}, function(results) {
615     results.forEach(function(result) {
616       var item = DownloadManager.getOrCreate(result);
617       item.div.hidden = false;
618     });
619     loading_older_span.hidden = true;
620   });
621 };
622
623 DownloadManager.onSearch = function() {
624   // split string by space, but ignore space in quotes
625   // http://stackoverflow.com/questions/16261635
626   var query = document.getElementById('q').value.match(/(?:[^\s"]+|"[^"]*")+/g);
627   if (!query) {
628     DownloadManager.showNew();
629     document.getElementById('search-zero').hidden = true;
630   } else {
631     query = query.map(function(term) {
632       // strip quotes
633       return (term.match(/\s/) &&
634               term[0].match(/["']/) &&
635               term[term.length - 1] == term[0]) ?
636         term.substr(1, term.length - 2) : term;
637     });
638     var searching = document.getElementById('searching');
639     searching.hidden = false;
640     chrome.downloads.search({query: query}, function(results) {
641       document.getElementById('older').hidden = true;
642       DownloadManager.forEachItem(function(item) {
643         item.div.hidden = true;
644       });
645       results.forEach(function(result) {
646         DownloadManager.getOrCreate(result).div.hidden = false;
647       });
648       searching.hidden = true;
649       document.getElementById('search-zero').hidden = (results.length != 0);
650     });
651   }
652 };
653
654 DownloadManager.clearAll = function() {
655   DownloadManager.forEachItem(function(item) {
656     if (!item.div.hidden) {
657       item.erase();
658       // The onErased handler should circle back around to loadItems.
659     }
660   });
661 };
662
663 var kShowNewMax = 50;
664 var kOldMs = 1000 * 60 * 60 * 24 * 7;
665
666 // These settings can be tuned by modifying localStorage in dev-tools.
667 if ('kShowNewMax' in localStorage) {
668   kShowNewMax = parseInt(localStorage.kShowNewMax);
669 }
670 if ('kOldMs' in localStorage) {
671   kOldMs = parseInt(localStorage.kOldMs);
672 }
673
674 DownloadManager.loadItems = function() {
675   // Request up to kShowNewMax + 1, but only display kShowNewMax; the +1 is a
676   // probe to see if there are any older downloads.
677   // TODO(benjhayden) When https://codereview.chromium.org/16924017/ is
678   // released, set minimum_chrome_version and remove this try/catch.
679   try {
680     chrome.downloads.search({
681         orderBy: ['-startTime'],
682         limit: kShowNewMax + 1},
683       function(results) {
684         DownloadManager.loadItems.items = results;
685         DownloadManager.loadItems.onLoaded();
686     });
687   } catch (exc) {
688     chrome.downloads.search({
689         orderBy: '-startTime',
690         limit: kShowNewMax + 1},
691       function(results) {
692         DownloadManager.loadItems.items = results;
693         DownloadManager.loadItems.onLoaded();
694     });
695   }
696 };
697 DownloadManager.loadItems.items = [];
698 DownloadManager.loadItems.window_loaded = false;
699
700 DownloadManager.loadItems.onLoaded = function() {
701   if (!DownloadManager.loadItems.window_loaded) {
702     return;
703   }
704   DownloadManager.loadItems.items.forEach(function(item) {
705     DownloadManager.getOrCreate(item);
706   });
707   DownloadManager.loadItems.items = [];
708   DownloadManager.showNew();
709 };
710
711 DownloadManager.loadItems.onWindowLoaded = function() {
712   DownloadManager.loadItems.window_loaded = true;
713   DownloadManager.loadItems.onLoaded();
714 };
715
716 // If this extension is installed on a stable-channel chrome, where the
717 // downloads API is not available, do not use the downloads API, and link to the
718 // beta channel.
719 if (chrome.downloads) {
720   // Start searching ASAP, don't wait for onload.
721   DownloadManager.loadItems();
722
723   chrome.downloads.onCreated.addListener(function(item) {
724     DownloadManager.getOrCreate(item);
725     DownloadManager.showNew();
726     DownloadManager.startPollingProgress();
727   });
728
729   chrome.downloads.onChanged.addListener(function(delta) {
730     var item = DownloadManager.getItem(delta.id);
731     if (item) {
732       item.onChanged(delta);
733     }
734   });
735
736   chrome.downloads.onErased.addListener(function(id) {
737     var item = DownloadManager.getItem(id);
738     if (!item) {
739       return;
740     }
741     item.onErased();
742     DownloadManager.loadItems();
743   });
744
745   window.onload = function() {
746     ratchetWidth(
747       document.getElementById('q-outer').offsetWidth +
748       document.getElementById('clear-all').offsetWidth +
749       document.getElementById('open-folder').offsetWidth);
750     setLastOpened();
751     loadI18nMessages();
752     DownloadManager.loadItems.onWindowLoaded();
753     document.getElementById('older').onclick = function() {
754       DownloadManager.showOlder();
755       return false;
756     };
757     document.getElementById('q').onsearch = function() {
758       DownloadManager.onSearch();
759     };
760     document.getElementById('clear-all').onclick = function() {
761       DownloadManager.clearAll();
762       return false;
763     };
764     if (chrome.downloads.showDefaultFolder) {
765       document.getElementById('open-folder').onclick = function() {
766         chrome.downloads.showDefaultFolder();
767         return false;
768       };
769     } else {
770       document.getElementById('open-folder').hidden = true;
771     }
772   };
773 } else {
774   // The downloads API is not available.
775   // TODO(benjhayden) Remove this when minimum_chrome_version is set.
776   window.onload = function() {
777     loadI18nMessages();
778     var bad_version = document.getElementById('bad-chrome-version');
779     bad_version.hidden = false;
780     bad_version.onclick = function() {
781       chrome.tabs.create({url: bad_version.href});
782       return false;
783     };
784     document.getElementById('empty').style.display = 'none';
785     document.getElementById('q').style.display = 'none';
786     document.getElementById('open-folder').style.display = 'none';
787     document.getElementById('clear-all').style.display = 'none';
788   };
789 }