理了一遍background.js, 加了一些注释。

var animationFrames = 36;

var animationSpeed = 10; // ms
var canvas = document.getElementById('canvas');
var loggedInImage = document.getElementById('logged_in');
var canvasContext = canvas.getContext('2d');
var pollIntervalMin = 5; // 5 minutes
var pollIntervalMax = 60; // 1 hour
var requestTimeout = 1000 * 2;// 2 seconds
var rotation = 0;
var loadingAnimation = new LoadingAnimation();

// Legacy support for pre-event-pages.
var oldChromeVersion = !chrome.runtime;
var requestTimerId;

function getGmailUrl() {
return "https://mail.google.com/mail/";
}

// Identifier used to debug the possibility of multiple instances of the
// extension making requests on behalf of a single user.
// localStorage与cookie类似,它是存储在客户端浏览器中的数据,
// 它与cookie不同的一点是它没有时间限制。
// localStorage属于html5中的新特性,在浏览器支持localStorage统计中,
// Chrome4+、Firefox3.5+、IE8+、IE7兼容模式、Safari4+、Opera10.5+都是支持localStorage的。
function getInstanceId() {
if (!localStorage.hasOwnProperty("instanceId"))
localStorage.instanceId = 'gmc' + parseInt(Date.now() * Math.random(), 10);
return localStorage.instanceId;
}

function getFeedUrl() {
// "zx" is a Gmail query parameter that is expected to contain a random
// string and may be ignored/stripped.
return getGmailUrl() + "feed/atom?zx=" + encodeURIComponent(getInstanceId());
}

function isGmailUrl(url) {
// Return whether the URL starts with the Gmail prefix.
return url.indexOf(getGmailUrl()) == 0;
}

// A "loading" animation displayed while we wait for the first response from
// Gmail. This animates the badge text with a dot that cycles from left to
// right.
function LoadingAnimation() {
this.timerId_ = 0;
this.maxCount_ = 8; // Total number of states in animation
this.current_ = 0; // Current state
this.maxDot_ = 4;// Max number of dots in animation
}
//原型方法
LoadingAnimation.prototype.paintFrame = function() {
var text = "";
for (var i = 0; i < this.maxDot_; i++) {
text += (i == this.current_) ? "." : " ";
}
if (this.current_ >= this.maxDot_)
text += "";

chrome.browserAction.setBadgeText({text:text});
this.current_++;
if (this.current_ == this.maxCount_)
this.current_ = 0;
}

LoadingAnimation.prototype.start = function() {
if (this.timerId_)
return;

var self = this;
this.timerId_ = window.setInterval(function() {
self.paintFrame();
}, 100);//0.1s
}

LoadingAnimation.prototype.stop = function() {
if (!this.timerId_)
return;

window.clearInterval(this.timerId_);
this.timerId_ = 0;
}

function updateIcon() {
//如果没有'unreadCount',表示离线
if (!localStorage.hasOwnProperty('unreadCount')) {
chrome.browserAction.setIcon({path:"gmail_not_logged_in.png"});
chrome.browserAction.setBadgeBackgroundColor({color:[190, 190, 190, 230]});
chrome.browserAction.setBadgeText({text:"?"});
} else {
chrome.browserAction.setIcon({path: "gmail_logged_in.png"});
chrome.browserAction.setBadgeBackgroundColor({color:[208, 0, 24, 255]});
chrome.browserAction.setBadgeText({
text: localStorage.unreadCount != "0" ? localStorage.unreadCount : ""
});
}
}

//当多次失败时,增加时间
function scheduleRequest() {
console.log('scheduleRequest');
var randomness = Math.random() * 2;//Math.random():产生一个[0,1)之间的随机数。
var exponent = Math.pow(2, localStorage.requestFailureCount || 0);
var multiplier = Math.max(randomness * exponent, 1);
var delay = Math.min(multiplier * pollIntervalMin, pollIntervalMax);
delay = Math.round(delay);
console.log('Scheduling for: ' + delay);

if (oldChromeVersion) {
if (requestTimerId) {
window.clearTimeout(requestTimerId);
}
requestTimerId = window.setTimeout(onAlarm, delay*60*1000);
} else {
console.log('Creating alarm');
// Use a repeating alarm so that it fires again if there was a problem
// setting the next alarm.
chrome.alarms.create('refresh', {periodInMinutes: delay});
}
}

// ajax stuff
function startRequest(params) {
// Schedule request immediately. We want to be sure to reschedule, even in the
// case where the extension process shuts down while this request is
// outstanding.
if (params && params.scheduleRequest) scheduleRequest();

function stopLoadingAnimation() {
if (params && params.showLoadingAnimation) loadingAnimation.stop();
}

if (params && params.showLoadingAnimation)
loadingAnimation.start();

getInboxCount(
function(count) {
stopLoadingAnimation();
updateUnreadCount(count); //成功后更新未读邮件数目
},
function() {
stopLoadingAnimation();
delete localStorage.unreadCount;//连接没成功,显示离线
updateIcon();
}
);
}

function getInboxCount(onSuccess, onError) {
var xhr = new XMLHttpRequest();
var abortTimerId = window.setTimeout(function() {
xhr.abort(); // synchronously calls onreadystatechange
}, requestTimeout);

function handleSuccess(count) {
localStorage.requestFailureCount = 0;
window.clearTimeout(abortTimerId);
if (onSuccess)
onSuccess(count);
}

var invokedErrorCallback = false;
function handleError() {
++localStorage.requestFailureCount;
window.clearTimeout(abortTimerId);
if (onError && !invokedErrorCallback)
onError();
invokedErrorCallback = true;
}

try {
xhr.onreadystatechange = function() {
//readyState一共有五个状态
//0 未连接
//1 打开连接
//2 发送请求
//3 交互
//4 完成交互,接手响应
if (xhr.readyState != 4)
return;

if (xhr.responseXML) {
var xmlDoc = xhr.responseXML;
//从 XML 文档中选取节点
//xmlDoc.evaluate(xpathText,contextNode,namespaceURLMapper,resultType,result)
//xpathText 表示要计算的 XPath 表达式的字符串。
//contextNode 文档中,对应要计算的表达式的节点。
//namespaceURLMapper
//把一个命名空间前缀映射为一个全称命名空间 URL 的函数。
//如果不需要这样的映射,就为 null。
//resultType
//指定了期待作为结果的对象的类型,使用 XPath 转换来强制结果类型。
//类型的可能的值是 XPathResult 对象所定义的常量。
//result
//一个复用的 XPathResult 对象;
//如果你要创建一个新的 XPathResult 对象,则为 null。
var fullCountSet = xmlDoc.evaluate("/gmail:feed/gmail:fullcount",
xmlDoc, gmailNSResolver, XPathResult.ANY_TYPE, null);
var fullCountNode = fullCountSet.iterateNext();
if (fullCountNode) {
handleSuccess(fullCountNode.textContent);
return;
} else {
console.error(chrome.i18n.getMessage("gmailcheck_node_error"));
}
}

handleError();
};

xhr.onerror = function(error) {
handleError();
};

//打开url 获得数据
xhr.open("GET", getFeedUrl(), true);
xhr.send(null);
} catch(e) {
console.error(chrome.i18n.getMessage("gmailcheck_exception", e));
handleError();
}
}

function gmailNSResolver(prefix) {
if(prefix == 'gmail') {
return 'http://purl.org/atom/ns#';
}
}

function updateUnreadCount(count) {
var changed = localStorage.unreadCount != count;
localStorage.unreadCount = count;
updateIcon();
if (changed)
animateFlip();
}
function ease(x) {
return (1-Math.sin(Math.PI/2+x*Math.PI))/2;
}

//动画显示
function animateFlip() {
rotation += 1/animationFrames;//animationFrames = 36
drawIconAtRotation();

if (rotation <= 1) {
setTimeout(animateFlip, animationSpeed);//animationSpeed=10
} else {
rotation = 0;
updateIcon();
}
}

function drawIconAtRotation() {
canvasContext.save();
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvasContext.translate(
Math.ceil(canvas.width/2),
Math.ceil(canvas.height/2)); //math.ceil(i)大于等于i的最小整
canvasContext.rotate(2*Math.PI*ease(rotation));
canvasContext.drawImage(loggedInImage,
-Math.ceil(canvas.width/2),
-Math.ceil(canvas.height/2));
canvasContext.restore();

chrome.browserAction.setIcon({imageData:canvasContext.getImageData(0, 0,
canvas.width,canvas.height)});
}
//进入gmail页面
function goToInbox() {
console.log('Going to inbox...');
//遍历tab页,如果已存在则选中并重载。否则新建标签
chrome.tabs.getAllInWindow(undefined, function(tabs) {
for (var i = 0, tab; tab = tabs[i]; i++) {
if (tab.url && isGmailUrl(tab.url)) {
console.log('Found Gmail tab: ' + tab.url + '. ' +
'Focusing and refreshing count...');
chrome.tabs.update(tab.id, {selected: true});
startRequest({scheduleRequest:false, showLoadingAnimation:false});
return;
}
}
console.log('Could not find Gmail tab. Creating one...');
chrome.tabs.create({url: getGmailUrl()});
});
}
//初始化
function onInit() {
console.log('onInit');
localStorage.requestFailureCount = 0; // used for exponential backoff
startRequest({scheduleRequest:true, showLoadingAnimation:true});
if (!oldChromeVersion) {
// TODO(mpcomplete): We should be able to remove this now, but leaving it
// for a little while just to be sure the refresh alarm is working nicely.
chrome.alarms.create('watchdog', {periodInMinutes:5});
}
}
//定时函数
function onAlarm(alarm) {
console.log('Got alarm', alarm);
// |alarm| can be undefined because onAlarm also gets called from
// window.setTimeout on old chrome versions.
if (alarm && alarm.name == 'watchdog') {
onWatchdog();
} else {
startRequest({scheduleRequest:true, showLoadingAnimation:false});
}
}

function onWatchdog() {
chrome.alarms.get('refresh', function(alarm) {
if (alarm) {
console.log('Refresh alarm exists. Yay.');
} else {
console.log('Refresh alarm doesn\'t exist!? ' +
'Refreshing now and rescheduling.');
startRequest({scheduleRequest:true, showLoadingAnimation:false});
}
});
}

//如果chrome未更新
if (oldChromeVersion) {
updateIcon();
onInit();
} else {
//当安装时被触发
chrome.runtime.onInstalled.addListener(onInit);
chrome.alarms.onAlarm.addListener(onAlarm);
}

var filters = {
// TODO(aa): Cannot use urlPrefix because all the url fields lack the protocol
// part. See crbug.com/140238.
url: [{urlContains: getGmailUrl().replace(/^https?\:\/\//, '')}]
};

//以下指定什么时候开始调用startRequest函数
function onNavigate(details) {
//如果是gmail页面,就更新
if (details.url && isGmailUrl(details.url)) {
console.log('Recognized Gmail navigation to: ' + details.url + '.' +
'Refreshing count...');
startRequest({scheduleRequest:false, showLoadingAnimation:false});
}
}
//当打开gmail页面,有更新时运行
if (chrome.webNavigation && chrome.webNavigation.onDOMContentLoaded &&
chrome.webNavigation.onReferenceFragmentUpdated) {
chrome.webNavigation.onDOMContentLoaded.addListener(onNavigate, filters);
chrome.webNavigation.onReferenceFragmentUpdated.addListener(
onNavigate, filters);
} else {
//当标签更新时,此事件被触发
chrome.tabs.onUpdated.addListener(function(_, details) {
onNavigate(details);
});
}

//图标被点击时进入gmail页面
chrome.browserAction.onClicked.addListener(goToInbox);

//当浏览器启动时,此事件被触发
if (chrome.runtime && chrome.runtime.onStartup) {
chrome.runtime.onStartup.addListener(function() {
console.log('Starting browser... updating icon.');
startRequest({scheduleRequest:false, showLoadingAnimation:false});
updateIcon();
});
} else {
// This hack is needed because Chrome 22 does not persist browserAction icon
// state, and also doesn't expose onStartup. So the icon always starts out in
// wrong state. We don't actually use onStartup except as a clue that we're
// in a version of Chrome that has this problem.
chrome.windows.onCreated.addListener(function() {
console.log('Window created... updating icon.');
startRequest({scheduleRequest:false, showLoadingAnimation:false});
updateIcon();
});
}

标签: none

评论已关闭