gmail loading progress bar 实现原理

  Gmail 登陆时,会显示一个progress bar, 显示加载的进度。

最先以为是模拟的效果,但是仔细观察发现,进度条是真实反映加载以及下载进度的,并不依赖网络状况。

所以非常好奇,因为在javascript中缺少检测文档下载进度的ApI(js的安全机制也禁止这样做),且只提供了加载开始,加载中,加载完成(或加载错误)等状态。那么,gmail 是如何实现实时监控加载百分比的呢?

  用firebug 监控 gmail登陆时的文件下载,可以找到一个get请求,该请求返回html文本,文件大小为300多k,在其中找到这样一个函数:

function _B_prog(pct){
top[
"pr"] = pct;
if (_B_thumbStyle_ === undefined) {
var thumb = top.document.getElementById("lpt");
_B_thumbStyle_
= thumb ? thumb.style : null
}
if (_B_thumbStyle_) {
_B_thumbStyle_.width
= Math.round(pct * 0.99) + "%";
if (pct == 100)
_B_thumbStyle_
= null
}
}

该函数在html body 后定义。定义可知,进度条由该函数动态实现。

再来看后面大段大段的<script>标签,标签中是压缩的js,你可以在每一段<script>定义最末找到调用_B_prog(pct)的语句,并且参数是从1

一直到100,如下所示:

<script>
var JS_START_TIME=(new Date).getTime(),GLOBALS=top.GLOBALS;if("o_6IqNZ5hNQ.zh_CN."!=GLOBALS[4])top.location.replace(top.location.href.split("#")[0]);function _B_log(imp,opt_val){var p="imp="+imp;if(arguments.length>1)p+="&val="+opt_val;_B_logImg_("jsle",p)}var loadTimes=[GLOBALS[0],GLOBALS[1],JS_START_TIME];function _B_record(){loadTimes.push((new Date).getTime())}var _B_thumbStyle_;
function _B_prog(pct){top["pr"]=pct;if(_B_thumbStyle_===undefined){var thumb=top.document.getElementById("lpt");_B_thumbStyle_=thumb?thumb.style:null}if(_B_thumbStyle_){_B_thumbStyle_.width=Math.round(pct*0.99)+"%";if(pct==100)_B_thumbStyle_=null}}function _B_err(e){var state=loadTimes.join("-");_B_logImg_("jserr","jsstate="+encodeURIComponent(state)+"&jsmsg="+encodeURIComponent(e));_B_handleError(e)}function _B_handleError(e){throw e;}
function _B_logImg_(v,p){(new Image).src="?ui=2&view="+v+"&"+p+"&ik="+GLOBALS[9]+"&random="+(new Date).getTime()}window.onerror=function(message,url,line){_B_err(message)};
_B_prog(1);
</script>
<script>
 ....
function Ala(b){var a=[];if(b)for(;b.isValidRow();)a.push(b.field(0)),b.next();return a}function Bla(b){return b&&b.isValidRow()?b.field(0):l}function Cla(b){if(b&&b.isValidRow()){for(var a={},c=b.fieldCount(),d=0;d<c;d++)a[b.fieldName(d)]=b.field(d);return a}else return l}function Dla(b){var a=[];if(b&&b.isValidRow())for(var c=b.fieldCount(),d=0;d<c;d++)a[d]=b.field(d);return a}
function Ela(b,a,c,d){if(c.length==0||d>=c.length)return b.execute(a);else{if(ka(c[d]))return b.execute(a,c[d]);c=Array.prototype.slice.call(c,d);return b.execute(a,c)}}function Fla(b,a,c,d,e){b=Ela(b,a,d,e);try{return c(b)}finally{b&&b.close()}}function Gla(b,a){var c,d;c=a?"ROLLBACK":"COMMIT";d=a?"beforerollback":"beforecommit";var e=b.dispatchEvent(new yla(d));if(e)b.ia.execute(c),b.ka=0,d=a?"rollback":"commit",b.dispatchEvent(new yla(d));return e}
function ll(b){a:{var a=b.nb;if(b.Ma)if(b.ka==0){b.Ba=!1;b.dispatchEvent(new yla("beforebegin"));b.ia.execute("BEGIN "+a);b.eb=Hla[a];b.ka=1;try{b.dispatchEvent(new yla("begin"))}catch(c){b.ia.execute("ROLLBACK"),b.ka=0,h(c)}b=!0;break a}else b.Ba?h(Error("=106")):Hla[a]>b.eb?h(Error("=107")):b.ka++;b=!1}return b}function ml(b){if(b.Ma)if(b.ka<=0&&h(Error("=108")),b.ka==1){var a=Gla(b,b.Ba);return!b.Ba&&a}else b.ka--;return!1}function Ila(){}function Jla(b,a){this.aa=b;this.ea=a}
function Kla(b){ri(this);this.Ta=b}var Lla=GLOBALS[0],Mla=GLOBALS[2],ol=GLOBALS[3],hf=GLOBALS[4],Nla=GLOBALS[5],pl=GLOBALS[6],ql=GLOBALS[7],Ola=GLOBALS[8],rl=GLOBALS[9],sl=GLOBALS[10],Pla=GLOBALS[11],Qla=GLOBALS[12],Rla=GLOBALS[14],tl=GLOBALS[15],ul=GLOBALS[16],Sla=GLOBALS[17],Tla=GLOBALS[18],vl=GLOBALS[19],Ula=GLOBALS[20],mja=GLOBALS[21],Vla=GLOBALS[22],Wla=GLOBALS[24],Xla=GLOBALS[25],Yla=GLOBALS[26],Zla=GLOBALS[27],ama=GLOBALS[28],bma=GLOBALS[29],cma=GLOBALS[30];
_B_prog(13)}catch(e){_B_err(e)}
</script>

。。。

一直到

<script>
 ...
hL.prototype.eR
=function $cvb(a,c){var d=this.ha,e=[];e[0]=w().toString(16);e[2]="0";e[18]=[a];e[24]=c;e=new Dr("^act",e);this.insertNode(mBa(d,e))};V(TLb,Er);
TLb.prototype.aa
=function $dvb(a,c){var d=this.Kn.Ua.ic();if(Zr(d)&&a=="iu"){vr(this.Kn.qj());d=this.Kn.Ua;Jh(d);gra(d);d=this.Kn;wr(d.Fe);d.Ua.Sc.ea();try{for(var e=1,f;f=c[e];e++){var g=new Dr("^act",f),i=mBa(d.Ua,g);d.Fe.Jb((new LC(g.Yr()[0])).dw());var k=d.Fe,m=i.get().Yr()[0][17];if(m)for(var q=0,u=j;u=m[q];q++)k.cO(u[8])&&(fb(m,q),q--);d.Fe.insertNode(i)}d.Fe.Ba();d.aa=!0}finally{if(d.Ua.Sc.aa(),f=d.Fe,f.Se==d)f.Se=l}return!0}return!1};
TLb.prototype.ea
=function $evb(a){var c=this.Kn.Ua.ic();Zr(c)&&a==0&&this.Kn.qj().dispatchEvent("Oe")};V(ULb,PAa);ULb.prototype.aa=function $fvb(){return Zr(this.Yi.ic())?new hL(this.Yi):l};V(VLb,HCa);VLb.prototype.ea=function $gvb(a,c){var d=a.getItem();c.yO=d.Sh("^act")};VLb.prototype.aa=function $hvb(a){if(a.yO){var c=new S;a.yO?Uj({Kc:"GV",title:"Buzz"},c):c.append("&nbsp;");return c.toString()}};O(N.Ja(),"t");R(N.Ja(),"t");
_B_prog(
100)}catch(e){_B_err(e)}
</script>

结果很明显,进度条就是这样每加载一段js,就调用进度函数来显示进度。

但是,等等,通常加载html时,并不是一段接着一段下载并加载,而是,等整个文件下载完成后再加载执行,这样就没有实时监控下载进度的效果了。

关键就是在这里,gmail并没有使用静态资源供客户端下载,而是通过类似servlet技术返回html。

这里用到了一点服务器推的技术,就是在每个</script>标签结束处调用PrintWriter.flush(), 在该方法之后,服务器端输出流会马上推送至客服端,但并没有关闭输出流,下次还可以再次调用flush(), 这样就可实现一段接一段将资源推送至客户端,实现进度监控了,而不是像目前webQQ一样模拟从0% ,然后等待,然后突然升到100%完成。

搞定,休息!

posted @ 2011-05-23 22:57  Jinker  阅读(4748)  评论(12编辑  收藏  举报