background.js revision d3868032626d59662ff73b372b5d584c1d144c53
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
5var colors = {
6  progressColor: '#0d0',
7  arrow: '#555',
8  danger: 'red',
9  complete: 'green',
10  paused: 'grey',
11  background: 'white',
12};
13
14function drawLine(ctx, x1, y1, x2, y2) {
15  ctx.beginPath();
16  ctx.moveTo(x1, y1);
17  ctx.lineTo(x2, y2);
18  ctx.stroke();
19}
20
21function drawProgressSpinner(ctx, stage) {
22  var center = ctx.canvas.width/2;
23  var radius = center*0.9;
24  const segments = 16;
25  var segArc = 2 * Math.PI / segments;
26  ctx.lineWidth = Math.round(ctx.canvas.width*0.1);
27  for (var seg = 0; seg < segments; ++seg) {
28    var color = ((stage == 'm') ? ((seg % 2) == 0) : (seg <= stage));
29    ctx.fillStyle = ctx.strokeStyle = (
30      color ? colors.progressColor : colors.background);
31    ctx.moveTo(center, center);
32    ctx.beginPath();
33    ctx.arc(center, center, radius, (seg-4)*segArc, (seg-3)*segArc, false);
34    ctx.lineTo(center, center);
35    ctx.fill();
36    ctx.stroke();
37  }
38  ctx.strokeStyle = colors.background;
39  radius += ctx.lineWidth-1;
40  drawLine(ctx, center-radius, center, center+radius, center);
41  drawLine(ctx, center, center-radius, center, center+radius);
42  radius *= Math.sin(Math.PI/4);
43  drawLine(ctx, center-radius, center-radius, center+radius, center+radius);
44  drawLine(ctx, center-radius, center+radius, center+radius, center-radius);
45}
46
47function drawArrow(ctx) {
48  ctx.beginPath();
49  ctx.lineWidth = Math.round(ctx.canvas.width*0.1);
50  ctx.lineJoin = 'round';
51  ctx.strokeStyle = ctx.fillStyle = colors.arrow;
52  var center = ctx.canvas.width/2;
53  var minw2 = center*0.2;
54  var maxw2 = center*0.60;
55  var height2 = maxw2;
56  ctx.moveTo(center-minw2, center-height2);
57  ctx.lineTo(center+minw2, center-height2);
58  ctx.lineTo(center+minw2, center);
59  ctx.lineTo(center+maxw2, center);
60  ctx.lineTo(center, center+height2);
61  ctx.lineTo(center-maxw2, center);
62  ctx.lineTo(center-minw2, center);
63  ctx.lineTo(center-minw2, center-height2);
64  ctx.lineTo(center+minw2, center-height2);
65  ctx.stroke();
66  ctx.fill();
67}
68
69function drawDangerBadge(ctx) {
70  var s = ctx.canvas.width/100;
71  ctx.fillStyle = colors.danger;
72  ctx.strokeStyle = colors.background;
73  ctx.lineWidth = Math.round(s*5);
74  var edge = ctx.canvas.width-ctx.lineWidth;
75  ctx.beginPath();
76  ctx.moveTo(s*75, s*55);
77  ctx.lineTo(edge, edge);
78  ctx.lineTo(s*55, edge);
79  ctx.lineTo(s*75, s*55);
80  ctx.lineTo(edge, edge);
81  ctx.fill();
82  ctx.stroke();
83}
84
85function drawPausedBadge(ctx) {
86  var s = ctx.canvas.width/100;
87  ctx.beginPath();
88  ctx.strokeStyle = colors.background;
89  ctx.lineWidth = Math.round(s*5);
90  ctx.rect(s*55, s*55, s*15, s*35);
91  ctx.fillStyle = colors.paused;
92  ctx.fill();
93  ctx.stroke();
94  ctx.rect(s*75, s*55, s*15, s*35);
95  ctx.fill();
96  ctx.stroke();
97}
98
99function drawCompleteBadge(ctx) {
100  var s = ctx.canvas.width/100;
101  ctx.beginPath();
102  ctx.arc(s*75, s*75, s*15, 0, 2*Math.PI, false);
103  ctx.fillStyle = colors.complete;
104  ctx.fill();
105  ctx.strokeStyle = colors.background;
106  ctx.lineWidth = Math.round(s*5);
107  ctx.stroke();
108}
109
110function drawIcon(side, stage, badge) {
111  var canvas = document.createElement('canvas');
112  canvas.width = canvas.height = side;
113  document.body.appendChild(canvas);
114  var ctx = canvas.getContext('2d');
115  if (stage != 'n') {
116    drawProgressSpinner(ctx, stage);
117  }
118  drawArrow(ctx);
119  if (badge == 'd') {
120    drawDangerBadge(ctx);
121  } else if (badge == 'p') {
122    drawPausedBadge(ctx);
123  } else if (badge == 'c') {
124    drawCompleteBadge(ctx);
125  }
126  return canvas;
127}
128
129function maybeOpen(id) {
130  var openWhenComplete = [];
131  try {
132    openWhenComplete = JSON.parse(localStorage.openWhenComplete);
133  } catch (e) {
134    localStorage.openWhenComplete = JSON.stringify(openWhenComplete);
135  }
136  var openNowIndex = openWhenComplete.indexOf(id);
137  if (openNowIndex >= 0) {
138    chrome.downloads.open(id);
139    openWhenComplete.splice(openNowIndex, 1);
140    localStorage.openWhenComplete = JSON.stringify(openWhenComplete);
141  }
142}
143
144function setBrowserActionIcon(stage, badge) {
145  var canvas1 = drawIcon(19, stage, badge);
146  var canvas2 = drawIcon(38, stage, badge);
147  var imageData = {};
148  imageData['' + canvas1.width] = canvas1.getContext('2d').getImageData(
149        0, 0, canvas1.width, canvas1.height);
150  imageData['' + canvas2.width] = canvas2.getContext('2d').getImageData(
151        0, 0, canvas2.width, canvas2.height);
152  chrome.browserAction.setIcon({imageData:imageData});
153  canvas1.parentNode.removeChild(canvas1);
154  canvas2.parentNode.removeChild(canvas2);
155}
156
157function pollProgress() {
158  pollProgress.tid = -1;
159  chrome.downloads.search({}, function(items) {
160    var popupLastOpened = parseInt(localStorage.popupLastOpened);
161    var totalTotalBytes = 0;
162    var totalBytesReceived = 0;
163    var anyMissingTotalBytes = false;
164    var anyDangerous = false;
165    var anyPaused = false;
166    var anyRecentlyCompleted = false;
167    var anyInProgress = false;
168    items.forEach(function(item) {
169      if (item.state == 'in_progress') {
170        anyInProgress = true;
171        if (item.totalBytes) {
172          totalTotalBytes += item.totalBytes;
173          totalBytesReceived += item.bytesReceived;
174        } else {
175          anyMissingTotalBytes = true;
176        }
177        var dangerous = ((item.danger != 'safe') &&
178                         (item.danger != 'accepted'));
179        anyDangerous = anyDangerous || dangerous;
180        anyPaused = anyPaused || item.paused;
181      } else if ((item.state == 'complete') && item.endTime && !item.error) {
182        var ended = (new Date(item.endTime)).getTime();
183        var recentlyCompleted = (ended >= popupLastOpened);
184        anyRecentlyCompleted = anyRecentlyCompleted || recentlyCompleted;
185        maybeOpen(item.id);
186      }
187    });
188    var stage = !anyInProgress ? 'n' : (anyMissingTotalBytes ? 'm' :
189      parseInt((totalBytesReceived / totalTotalBytes) * 15));
190    var badge = anyDangerous ? 'd' : (anyPaused ? 'p' :
191      (anyRecentlyCompleted ? 'c' : ''));
192
193    var targetIcon = stage + ' ' + badge;
194    if (sessionStorage.currentIcon != targetIcon) {
195      setBrowserActionIcon(stage, badge);
196      sessionStorage.currentIcon = targetIcon;
197    }
198
199    if (anyInProgress &&
200        (pollProgress.tid < 0)) {
201      pollProgress.start();
202    }
203  });
204}
205pollProgress.tid = -1;
206pollProgress.MS = 200;
207
208pollProgress.start = function() {
209  if (pollProgress.tid < 0) {
210    pollProgress.tid = setTimeout(pollProgress, pollProgress.MS);
211  }
212};
213
214function isNumber(n) {
215  return !isNaN(parseFloat(n)) && isFinite(n);
216}
217
218if (!isNumber(localStorage.popupLastOpened)) {
219  localStorage.popupLastOpened = '' + (new Date()).getTime();
220}
221
222chrome.downloads.onCreated.addListener(function(item) {
223  pollProgress();
224});
225
226pollProgress();
227
228function openWhenComplete(downloadId) {
229  var ids = [];
230  try {
231    ids = JSON.parse(localStorage.openWhenComplete);
232  } catch (e) {
233    localStorage.openWhenComplete = JSON.stringify(ids);
234  }
235  pollProgress.start();
236  if (ids.indexOf(downloadId) >= 0) {
237    return;
238  }
239  ids.push(downloadId);
240  localStorage.openWhenComplete = JSON.stringify(ids);
241}
242
243chrome.runtime.onMessage.addListener(function(request) {
244  if (request == 'poll') {
245    pollProgress.start();
246  }
247  if (request == 'icons') {
248    [16, 19, 38, 128].forEach(function(s) {
249      var canvas = drawIcon(s, 'n', '');
250      chrome.downloads.download({
251        url: canvas.toDataURL('image/png', 1.0),
252        filename: 'icon' + s + '.png',
253      });
254      canvas.parentNode.removeChild(canvas);
255    });
256  }
257  if (isNumber(request.openWhenComplete)) {
258    openWhenComplete(request.openWhenComplete);
259  }
260});
261