1<html> 2<head> 3<script> 4var animationFrames = 36; 5var animationSpeed = 10; // ms 6var canvas; 7var canvasContext; 8var loggedInImage; 9var pollIntervalMin = 1000 * 60; // 1 minute 10var pollIntervalMax = 1000 * 60 * 60; // 1 hour 11var requestFailureCount = 0; // used for exponential backoff 12var requestTimeout = 1000 * 2; // 5 seconds 13var rotation = 0; 14var unreadCount = -1; 15var loadingAnimation = new LoadingAnimation(); 16 17function getGmailUrl() { 18 var url = "https://mail.google.com/"; 19 if (localStorage.customDomain) 20 url += localStorage.customDomain + "/"; 21 else 22 url += "mail/" 23 return url; 24} 25 26function getFeedUrl() { 27 return getGmailUrl() + "feed/atom"; 28} 29 30function isGmailUrl(url) { 31 // This is the Gmail we're looking for if: 32 // - starts with the correct gmail url 33 // - doesn't contain any other path chars 34 var gmail = getGmailUrl(); 35 if (url.indexOf(gmail) != 0) 36 return false; 37 38 return url.length == gmail.length || url[gmail.length] == '?' || 39 url[gmail.length] == '#'; 40} 41 42// A "loading" animation displayed while we wait for the first response from 43// Gmail. This animates the badge text with a dot that cycles from left to 44// right. 45function LoadingAnimation() { 46 this.timerId_ = 0; 47 this.maxCount_ = 8; // Total number of states in animation 48 this.current_ = 0; // Current state 49 this.maxDot_ = 4; // Max number of dots in animation 50} 51 52LoadingAnimation.prototype.paintFrame = function() { 53 var text = ""; 54 for (var i = 0; i < this.maxDot_; i++) { 55 text += (i == this.current_) ? "." : " "; 56 } 57 if (this.current_ >= this.maxDot_) 58 text += ""; 59 60 chrome.browserAction.setBadgeText({text:text}); 61 this.current_++; 62 if (this.current_ == this.maxCount_) 63 this.current_ = 0; 64} 65 66LoadingAnimation.prototype.start = function() { 67 if (this.timerId_) 68 return; 69 70 var self = this; 71 this.timerId_ = window.setInterval(function() { 72 self.paintFrame(); 73 }, 100); 74} 75 76LoadingAnimation.prototype.stop = function() { 77 if (!this.timerId_) 78 return; 79 80 window.clearInterval(this.timerId_); 81 this.timerId_ = 0; 82} 83 84 85chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) { 86 if (changeInfo.url && isGmailUrl(changeInfo.url)) { 87 getInboxCount(function(count) { 88 updateUnreadCount(count); 89 }); 90 } 91}); 92 93 94function init() { 95 canvas = document.getElementById('canvas'); 96 loggedInImage = document.getElementById('logged_in'); 97 canvasContext = canvas.getContext('2d'); 98 99 chrome.browserAction.setBadgeBackgroundColor({color:[208, 0, 24, 255]}); 100 chrome.browserAction.setIcon({path: "gmail_logged_in.png"}); 101 loadingAnimation.start(); 102 103 startRequest(); 104} 105 106function scheduleRequest() { 107 var randomness = Math.random() * 2; 108 var exponent = Math.pow(2, requestFailureCount); 109 var delay = Math.min(randomness * pollIntervalMin * exponent, 110 pollIntervalMax); 111 delay = Math.round(delay); 112 113 window.setTimeout(startRequest, delay); 114} 115 116// ajax stuff 117function startRequest() { 118 getInboxCount( 119 function(count) { 120 loadingAnimation.stop(); 121 updateUnreadCount(count); 122 scheduleRequest(); 123 }, 124 function() { 125 loadingAnimation.stop(); 126 showLoggedOut(); 127 scheduleRequest(); 128 } 129 ); 130} 131 132function getInboxCount(onSuccess, onError) { 133 var xhr = new XMLHttpRequest(); 134 var abortTimerId = window.setTimeout(function() { 135 xhr.abort(); // synchronously calls onreadystatechange 136 }, requestTimeout); 137 138 function handleSuccess(count) { 139 requestFailureCount = 0; 140 window.clearTimeout(abortTimerId); 141 if (onSuccess) 142 onSuccess(count); 143 } 144 145 function handleError() { 146 ++requestFailureCount; 147 window.clearTimeout(abortTimerId); 148 if (onError) 149 onError(); 150 } 151 152 try { 153 xhr.onreadystatechange = function(){ 154 if (xhr.readyState != 4) 155 return; 156 157 if (xhr.responseXML) { 158 var xmlDoc = xhr.responseXML; 159 var fullCountSet = xmlDoc.evaluate("/gmail:feed/gmail:fullcount", 160 xmlDoc, gmailNSResolver, XPathResult.ANY_TYPE, null); 161 var fullCountNode = fullCountSet.iterateNext(); 162 if (fullCountNode) { 163 handleSuccess(fullCountNode.textContent); 164 return; 165 } else { 166 console.error(chrome.i18n.getMessage("gmailcheck_node_error")); 167 } 168 } 169 170 handleError(); 171 } 172 173 xhr.onerror = function(error) { 174 handleError(); 175 } 176 177 xhr.open("GET", getFeedUrl(), true); 178 xhr.send(null); 179 } catch(e) { 180 console.error(chrome.i18n.getMessage("gmailcheck_exception", e)); 181 handleError(); 182 } 183} 184 185function gmailNSResolver(prefix) { 186 if(prefix == 'gmail') { 187 return 'http://purl.org/atom/ns#'; 188 } 189} 190 191function updateUnreadCount(count) { 192 if (unreadCount != count) { 193 unreadCount = count; 194 animateFlip(); 195 } 196} 197 198 199function ease(x) { 200 return (1-Math.sin(Math.PI/2+x*Math.PI))/2; 201} 202 203function animateFlip() { 204 rotation += 1/animationFrames; 205 drawIconAtRotation(); 206 207 if (rotation <= 1) { 208 setTimeout("animateFlip()", animationSpeed); 209 } else { 210 rotation = 0; 211 drawIconAtRotation(); 212 chrome.browserAction.setBadgeText({ 213 text: unreadCount != "0" ? unreadCount : "" 214 }); 215 chrome.browserAction.setBadgeBackgroundColor({color:[208, 0, 24, 255]}); 216 } 217} 218 219function showLoggedOut() { 220 unreadCount = -1; 221 chrome.browserAction.setIcon({path:"gmail_not_logged_in.png"}); 222 chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]}); 223 chrome.browserAction.setBadgeText({text:"?"}); 224} 225 226function drawIconAtRotation() { 227 canvasContext.save(); 228 canvasContext.clearRect(0, 0, canvas.width, canvas.height); 229 canvasContext.translate( 230 Math.ceil(canvas.width/2), 231 Math.ceil(canvas.height/2)); 232 canvasContext.rotate(2*Math.PI*ease(rotation)); 233 canvasContext.drawImage(loggedInImage, 234 -Math.ceil(canvas.width/2), 235 -Math.ceil(canvas.height/2)); 236 canvasContext.restore(); 237 238 chrome.browserAction.setIcon({imageData:canvasContext.getImageData(0, 0, 239 canvas.width,canvas.height)}); 240} 241 242function goToInbox() { 243 chrome.tabs.getAllInWindow(undefined, function(tabs) { 244 for (var i = 0, tab; tab = tabs[i]; i++) { 245 if (tab.url && isGmailUrl(tab.url)) { 246 chrome.tabs.update(tab.id, {selected: true}); 247 return; 248 } 249 } 250 chrome.tabs.create({url: getGmailUrl()}); 251 }); 252} 253 254// Called when the user clicks on the browser action. 255chrome.browserAction.onClicked.addListener(function(tab) { 256 goToInbox(); 257}); 258 259</script> 260</head> 261<body onload="init()"> 262<img id="logged_in" src="gmail_logged_in.png"> 263<canvas id="canvas" width="19" height="19"> 264</body> 265</html> 266 267