虽然代码分析了很多,但是还没有真正做check的工作,下面就来找到真正的checker。
代码位置:gecko/toolkit/mozapps/update/nsUpdateService.js。参考之前的(FFOS Gecko & Gaia) OTA - 代码模块总览,nsUpdateService.js中的Checker对象,实现了nsIUpdateChecker这个interface。下面就来分析Checker对象的实现。
1. checkForUpdates函数:
实现略长,在代码中添加注释分析。
checkForUpdates: function UC_checkForUpdates(listener, force) { LOG("Checker: checkForUpdates, force: " + force); if (!listener) throw Cr.NS_ERROR_NULL_POINTER; // 通知‘update-check-start‘事件,监听者是谁呢?还记得上一篇分析的UpdatePrompt实现了nsIObserver吗? Services.obs.notifyObservers(null, "update-check-start", null); // 这个函数很重要,它通过获取很多settings和preferences,还会通过libutils库,来获取很多系统参数,拼接成一个url, // 这个url就是OTA server URL var url = this.getUpdateURL(force); if (!url || (!this.enabled && !force)) return; // 通过XHR发送GET请求,获取update信息 this._request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]. createInstance(Ci.nsISupports); // This is here to let unit test code override XHR if (this._request.wrappedJSObject) { this._request = this._request.wrappedJSObject; } this._request.open("GET", url, true); var allowNonBuiltIn = !getPref("getBoolPref", PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); this._request.channel.notificationCallbacks = new gCertUtils.BadCertHandler(allowNonBuiltIn); // Prevent the request from reading from the cache. this._request.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE; // Prevent the request from writing to the cache. this._request.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; this._request.overrideMimeType("text/xml"); // The Cache-Control header is only interpreted by proxies and the // final destination. It does not help if a resource is already // cached locally. this._request.setRequestHeader("Cache-Control", "no-cache"); // HTTP/1.0 servers might not implement Cache-Control and // might only implement Pragma: no-cache this._request.setRequestHeader("Pragma", "no-cache"); var self = this; // 设置XHR event listener this._request.addEventListener("error", function(event) { self.onError(event); } ,false); this._request.addEventListener("load", function(event) { self.onLoad(event); }, false); LOG("Checker:checkForUpdates - sending request to: " + url); this._request.send(null); // 将传进来的listener保存下来,在XHR的event handler中回调 this._callback = listener; },
2. onLoad函数
当request请求返回时,会回调onLoad函数
onLoad: function UC_onLoad(event) { LOG("Checker:onLoad - request completed downloading document"); var prefs = Services.prefs; var certs = null; if (!getPref("getCharPref", PREF_APP_UPDATE_URL_OVERRIDE, null) && getPref("getBoolPref", PREF_APP_UPDATE_CERT_CHECKATTRS, true)) { certs = gCertUtils.readCertPrefs(PREF_APP_UPDATE_CERTS_BRANCH); } try { // Analyze the resulting DOM and determine the set of updates. // 之前没有找到对于request response解析的部分,其实秘密就在于这里,Checker中并没有显示的定义_updates属性,而是定义了一个get _updates()函数。 // 这是js的特性,如果定义了get/set property()函数,就表示可以get/set这个以property命名的属性。 var updates = this._updates; LOG("Checker:onLoad - number of updates available: " + updates.length); var allowNonBuiltIn = !getPref("getBoolPref", PREF_APP_UPDATE_CERT_REQUIREBUILTIN, true); gCertUtils.checkCert(this._request.channel, allowNonBuiltIn, certs); if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_CERT_ERRORS)) Services.prefs.clearUserPref(PREF_APP_UPDATE_CERT_ERRORS); if (Services.prefs.prefHasUserValue(PREF_APP_UPDATE_BACKGROUNDERRORS)) Services.prefs.clearUserPref(PREF_APP_UPDATE_BACKGROUNDERRORS); // Tell the callback about the updates this._callback.onCheckComplete(event.target, updates, updates.length); } catch (e) { LOG("Checker:onLoad - there was a problem checking for updates. " + "Exception: " + e); var request = event.target; var status = this._getChannelStatus(request); LOG("Checker:onLoad - request.status: " + status); var update = new Update(null); update.errorCode = status; update.statusText = getStatusTextFromCode(status, 404); if (this._isHttpStatusCode(status)) { update.errorCode = HTTP_ERROR_OFFSET + status; } if (e.result && e.result == Cr.NS_ERROR_ILLEGAL_VALUE) { update.errorCode = updates[0] ? CERT_ATTR_CHECK_FAILED_HAS_UPDATE : CERT_ATTR_CHECK_FAILED_NO_UPDATE; } this._callback.onError(request, update); } this._callback = null; this._request = null; },
3. get _updates()函数
解析request response,根据mozilla的文档,这个response是一个update.xml文件,具体的格式请参考https://wiki.mozilla.org/Software_Update:updates.xml_Format。
通过解析update.xml文件,返回一个nsIUpdate数组,也就是Update对象的数组。Update的构造函数又会解析update.xml中的所有patch标签,每个patch标签会对应的生成一个nsIUpdatePatch,也就是UpdatePatch,也就是说Update对象里会包含1-多个UpdatePatch对象,并且Update对象里会保存一些其他的信息,比如是否为OS Update等,具体的解析过程请参考代码。
/**
* Returns an array of nsIUpdate objects discovered by the update check.
* @throws if the XML document element node name is not updates.
*/
get _updates() { var updatesElement = this._request.responseXML.documentElement; if (!updatesElement) { LOG("Checker:_updates get - empty updates document?!"); return []; } if (updatesElement.nodeName != "updates") { LOG("Checker:_updates get - unexpected node name!"); throw new Error("Unexpected node name, expected: updates, got: " + updatesElement.nodeName); } const ELEMENT_NODE = Ci.nsIDOMNode.ELEMENT_NODE; var updates = []; for (var i = 0; i < updatesElement.childNodes.length; ++i) { var updateElement = updatesElement.childNodes.item(i); if (updateElement.nodeType != ELEMENT_NODE || updateElement.localName != "update") continue; updateElement.QueryInterface(Ci.nsIDOMElement); let update; try { update = new Update(updateElement); } catch (e) { LOG("Checker:_updates get - invalid <update/>, ignoring..."); continue; } update.serviceURL = this.getUpdateURL(this._forced); update.channel = UpdateChannel.get(); updates.push(update); } return updates; },