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