popup.js revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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
5function 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
12function setLastOpened() {
13  localStorage.popupLastOpened = (new Date()).getTime();
14  chrome.runtime.sendMessage('poll');
15};
16
17function 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
63function getTextWidth(s) {
64  var probe = document.getElementById('text-width-probe');
65  probe.innerText = s;
66  return probe.offsetWidth;
67};
68
69function 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
86function 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
102function 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
123function ratchetWidth(w) {
124  var current = parseInt(document.body.style.minWidth) || 0;
125  document.body.style.minWidth = Math.max(w, current) + 'px';
126}
127
128function ratchetHeight(h) {
129  var current = parseInt(document.body.style.minHeight) || 0;
130  document.body.style.minHeight = Math.max(h, current) + 'px';
131}
132
133function 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
149function arrayFrom(seq) {
150  return Array.prototype.slice.apply(seq);
151};
152
153function 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}
273DownloadItem.canResumeHack = false;
274
275DownloadItem.prototype.getElement = function(name) {
276  return document.querySelector('#item' + this.id + ' .' + name);
277};
278
279DownloadItem.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
433DownloadItem.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
448DownloadItem.prototype.onErased = function() {
449  window.removeEventListener('mousemove', this.more_mousemove);
450  document.getElementById('items').removeChild(this.div);
451};
452
453DownloadItem.prototype.drag = function() {
454  chrome.downloads.drag(this.id);
455};
456
457DownloadItem.prototype.show = function() {
458  chrome.downloads.show(this.id);
459};
460
461DownloadItem.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
469DownloadItem.prototype.removeFile = function() {
470  chrome.downloads.removeFile(this.id);
471  this.deleted = true;
472  this.render();
473};
474
475DownloadItem.prototype.erase = function() {
476  chrome.downloads.erase({id: this.id});
477};
478
479DownloadItem.prototype.pause = function() {
480  chrome.downloads.pause(this.id);
481};
482
483DownloadItem.prototype.resume = function() {
484  chrome.downloads.resume(this.id);
485};
486
487DownloadItem.prototype.cancel = function() {
488  chrome.downloads.cancel(this.id);
489};
490
491DownloadItem.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  var id = this.id;
509  setTimeout(function() {
510    chrome.downloads.acceptDanger(id, function() {
511      DownloadItem.prototype.maybeAccept.accepting_danger = false;
512      arrayFrom(document.getElementById('items').childNodes).forEach(
513        function(item_div) { item_div.item.maybeAccept(); });
514    });
515  }, 500);
516};
517DownloadItem.prototype.maybeAccept.accepting_danger = false;
518
519var DownloadManager = {};
520
521DownloadManager.showingOlder = false;
522
523DownloadManager.getItem = function(id) {
524  var item_div = document.getElementById('item' + id);
525  return item_div ? item_div.item : null;
526};
527
528DownloadManager.getOrCreate = function(data) {
529  var item = DownloadManager.getItem(data.id);
530  return item ? item : new DownloadItem(data);
531};
532
533DownloadManager.forEachItem = function(cb) {
534  // Calls cb(item, index) in the order that they are displayed, i.e. in order
535  // of decreasing startTime.
536  arrayFrom(document.getElementById('items').childNodes).forEach(
537    function(item_div, index) { cb(item_div.item, index); });
538};
539
540DownloadManager.startPollingProgress = function() {
541  if (DownloadManager.startPollingProgress.tid < 0) {
542    DownloadManager.startPollingProgress.tid = setTimeout(
543      DownloadManager.startPollingProgress.pollProgress,
544      DownloadManager.startPollingProgress.MS);
545  }
546}
547DownloadManager.startPollingProgress.MS = 200;
548DownloadManager.startPollingProgress.tid = -1;
549DownloadManager.startPollingProgress.pollProgress = function() {
550  DownloadManager.startPollingProgress.tid = -1;
551  chrome.downloads.search({state: 'in_progress', paused: false},
552      function(results) {
553    if (!results.length)
554      return;
555    results.forEach(function(result) {
556      var item = DownloadManager.getOrCreate(result);
557      for (var prop in result) {
558        item[prop] = result[prop];
559      }
560      item.render();
561      if ((item.state == 'in_progress') && !item.paused) {
562        DownloadManager.startPollingProgress();
563      }
564    });
565  });
566};
567
568DownloadManager.showNew = function() {
569  var any_items = (document.getElementById('items').childNodes.length > 0);
570  document.getElementById('empty').style.display =
571    any_items ? 'none' : 'inline-block';
572  document.getElementById('head').style.borderBottomWidth =
573    (any_items ? 1 : 0) + 'px';
574  document.getElementById('clear-all').hidden = !any_items;
575
576  var query_search = document.getElementById('q');
577  query_search.hidden = !any_items;
578
579  if (!any_items) {
580    return;
581  }
582  var old_ms = (new Date()).getTime() - kOldMs;
583  var any_hidden = false;
584  var any_showing = false;
585  // First show up to kShowNewMax items newer than kOldMs. If there aren't any
586  // items newer than kOldMs, then show up to kShowNewMax items of any age. If
587  // there are any hidden items, show the Show Older button.
588  DownloadManager.forEachItem(function(item, index) {
589    item.div.hidden = !DownloadManager.showingOlder && (
590      (item.startTime.getTime() < old_ms) || (index >= kShowNewMax));
591    any_hidden = any_hidden || item.div.hidden;
592    any_showing = any_showing || !item.div.hidden;
593  });
594  if (!any_showing) {
595    any_hidden = false;
596    DownloadManager.forEachItem(function(item, index) {
597      item.div.hidden = !DownloadManager.showingOlder && (index >= kShowNewMax);
598      any_hidden = any_hidden || item.div.hidden;
599      any_showing = any_showing || !item.div.hidden;
600    });
601  }
602  document.getElementById('older').hidden = !any_hidden;
603
604  query_search.focus();
605};
606
607DownloadManager.showOlder = function() {
608  DownloadManager.showingOlder = true;
609  var loading_older_span = document.getElementById('loading-older');
610  document.getElementById('older').hidden = true;
611  loading_older_span.hidden = false;
612  chrome.downloads.search({}, function(results) {
613    results.forEach(function(result) {
614      var item = DownloadManager.getOrCreate(result);
615      item.div.hidden = false;
616    });
617    loading_older_span.hidden = true;
618  });
619};
620
621DownloadManager.onSearch = function() {
622  // split string by space, but ignore space in quotes
623  // http://stackoverflow.com/questions/16261635
624  var query = document.getElementById('q').value.match(/(?:[^\s"]+|"[^"]*")+/g);
625  if (!query) {
626    DownloadManager.showNew();
627    document.getElementById('search-zero').hidden = true;
628  } else {
629    query = query.map(function(term) {
630      // strip quotes
631      return (term.match(/\s/) &&
632              term[0].match(/["']/) &&
633              term[term.length - 1] == term[0]) ?
634        term.substr(1, term.length - 2) : term;
635    });
636    var searching = document.getElementById('searching');
637    searching.hidden = false;
638    chrome.downloads.search({query: query}, function(results) {
639      document.getElementById('older').hidden = true;
640      DownloadManager.forEachItem(function(item) {
641        item.div.hidden = true;
642      });
643      results.forEach(function(result) {
644        DownloadManager.getOrCreate(result).div.hidden = false;
645      });
646      searching.hidden = true;
647      document.getElementById('search-zero').hidden = (results.length != 0);
648    });
649  }
650};
651
652DownloadManager.clearAll = function() {
653  DownloadManager.forEachItem(function(item) {
654    if (!item.div.hidden) {
655      item.erase();
656      // The onErased handler should circle back around to loadItems.
657    }
658  });
659};
660
661var kShowNewMax = 50;
662var kOldMs = 1000 * 60 * 60 * 24 * 7;
663
664// These settings can be tuned by modifying localStorage in dev-tools.
665if ('kShowNewMax' in localStorage) {
666  kShowNewMax = parseInt(localStorage.kShowNewMax);
667}
668if ('kOldMs' in localStorage) {
669  kOldMs = parseInt(localStorage.kOldMs);
670}
671
672DownloadManager.loadItems = function() {
673  // Request up to kShowNewMax + 1, but only display kShowNewMax; the +1 is a
674  // probe to see if there are any older downloads.
675  // TODO(benjhayden) When https://codereview.chromium.org/16924017/ is
676  // released, set minimum_chrome_version and remove this try/catch.
677  try {
678    chrome.downloads.search({
679        orderBy: ['-startTime'],
680        limit: kShowNewMax + 1},
681      function(results) {
682        DownloadManager.loadItems.items = results;
683        DownloadManager.loadItems.onLoaded();
684    });
685  } catch (exc) {
686    chrome.downloads.search({
687        orderBy: '-startTime',
688        limit: kShowNewMax + 1},
689      function(results) {
690        DownloadManager.loadItems.items = results;
691        DownloadManager.loadItems.onLoaded();
692    });
693  }
694};
695DownloadManager.loadItems.items = [];
696DownloadManager.loadItems.window_loaded = false;
697
698DownloadManager.loadItems.onLoaded = function() {
699  if (!DownloadManager.loadItems.window_loaded) {
700    return;
701  }
702  DownloadManager.loadItems.items.forEach(function(item) {
703    DownloadManager.getOrCreate(item);
704  });
705  DownloadManager.loadItems.items = [];
706  DownloadManager.showNew();
707};
708
709DownloadManager.loadItems.onWindowLoaded = function() {
710  DownloadManager.loadItems.window_loaded = true;
711  DownloadManager.loadItems.onLoaded();
712};
713
714// If this extension is installed on a stable-channel chrome, where the
715// downloads API is not available, do not use the downloads API, and link to the
716// beta channel.
717if (chrome.downloads) {
718  // Start searching ASAP, don't wait for onload.
719  DownloadManager.loadItems();
720
721  chrome.downloads.onCreated.addListener(function(item) {
722    DownloadManager.getOrCreate(item);
723    DownloadManager.showNew();
724    DownloadManager.startPollingProgress();
725  });
726
727  chrome.downloads.onChanged.addListener(function(delta) {
728    var item = DownloadManager.getItem(delta.id);
729    if (item) {
730      item.onChanged(delta);
731    }
732  });
733
734  chrome.downloads.onErased.addListener(function(id) {
735    var item = DownloadManager.getItem(id);
736    if (!item) {
737      return;
738    }
739    item.onErased();
740    DownloadManager.loadItems();
741  });
742
743  window.onload = function() {
744    ratchetWidth(
745      document.getElementById('q-outer').offsetWidth +
746      document.getElementById('clear-all').offsetWidth +
747      document.getElementById('open-folder').offsetWidth);
748    setLastOpened();
749    loadI18nMessages();
750    DownloadManager.loadItems.onWindowLoaded();
751    document.getElementById('older').onclick = function() {
752      DownloadManager.showOlder();
753      return false;
754    };
755    document.getElementById('q').onsearch = function() {
756      DownloadManager.onSearch();
757    };
758    document.getElementById('clear-all').onclick = function() {
759      DownloadManager.clearAll();
760      return false;
761    };
762    if (chrome.downloads.showDefaultFolder) {
763      document.getElementById('open-folder').onclick = function() {
764        chrome.downloads.showDefaultFolder();
765        return false;
766      };
767    } else {
768      document.getElementById('open-folder').hidden = true;
769    }
770  };
771} else {
772  // The downloads API is not available.
773  // TODO(benjhayden) Remove this when minimum_chrome_version is set.
774  window.onload = function() {
775    loadI18nMessages();
776    var bad_version = document.getElementById('bad-chrome-version');
777    bad_version.hidden = false;
778    bad_version.onclick = function() {
779      chrome.tabs.create({url: bad_version.href});
780      return false;
781    };
782    document.getElementById('empty').style.display = 'none';
783    document.getElementById('q').style.display = 'none';
784    document.getElementById('open-folder').style.display = 'none';
785    document.getElementById('clear-all').style.display = 'none';
786  };
787}
788