BatteryManager提供了訪問系統電源管理級別信息的方式。node
navigator.getBattery返回一個battery promise,你能夠用這個經過這個promise來與Battery Status API進行交互。git
navigator.getBattery().then(function(battery){web
console.log("Battery charging? "+(battery.charging ?"Yes":"No"));chrome console.log("Battery level: "+ battery.level *100+"%");shell console.log("Battery charging time: "+ battery.chargingTime +" seconds");數據庫 console.log("Battery discharging time: "+ battery.dischargingTime +" seconds");編程
battery.addEventListener('chargingchange', function(){json console.log("Battery charging? "+(battery.charging ?"Yes":"No"));api });數組
battery.addEventListener('levelchange', function(){ console.log("Battery level: "+ battery.level *100+"%"); });
battery.addEventListener('chargingtimechange', function(){ console.log("Battery charging time: "+ battery.chargingTime +" seconds"); });
battery.addEventListener('dischargingtimechange', function(){ console.log("Battery discharging time: "+ battery.dischargingTime +" seconds"); });
}); |
當js代碼執行到"app://system.gaiamobile.org/js/battery_manager.js":14中的_battery: window.navigator.battery時候,會建立C++的BatteryManager對象(其對應的實如今BatteryManager.cpp (gecko\dom\battery)文件中):
(gdb) call DumpJSStack() 0<TOP LEVEL>["app://system.gaiamobile.org/js/battery_manager.js":14] this=[object Window] |
對應的C++代碼代碼執行堆棧以下:
#0 mozilla::hal_impl::GetCurrentBatteryInformation ( aBatteryInfo=0xb64162b8<mozilla::hal::sBatteryObservers+8>) at ../../gecko/hal/gonk/GonkHal.cpp:438 #1 0xb4f59f1c in GetCurrentInformation ( this=0xb64162b0<mozilla::hal::sBatteryObservers>) at ../../gecko/hal/Hal.cpp:248 #2 mozilla::hal::GetCurrentBatteryInformation (aInfo=aInfo@entry=0xbecef7e0) at ../../gecko/hal/Hal.cpp:360 #3 0xb53bbdb8 in mozilla::dom::battery::BatteryManager::Init (this=0xa9aebc80) at ../../../gecko/dom/battery/BatteryManager.cpp:42 #4 0xb53a08ce in mozilla::dom::Navigator::GetBattery (this=0xa96d2620, aRv=...) at ../../../gecko/dom/base/Navigator.cpp:1410 #5 0xb5208970 in mozilla::dom::NavigatorBinding::get_battery (cx=0xaa4c0f80, obj=..., self=<optimized out>, args=...) at NavigatorBinding.cpp:1596 #6 0xb52bdefa in mozilla::dom::GenericBindingGetter (cx=0xaa4c0f80, argc=<optimized out>, vp=<optimized out>) at ../../../gecko/dom/bindings/BindingUtils.cpp:2217 #7 0xb5c8dcd2 in CallJSNative (args=..., native=0xb52bde65<mozilla::dom::GenericBindingGetter(JSContext*,unsignedint, JS::Value*)>, cx=0xaa4c0f80) at ../../../gecko/js/src/jscntxtinlines.h:239 #8 js::Invoke (cx=cx@entry=0xaa4c0f80, args=..., |
會從hal::BatteryInformation中獲取level,charging,remainingTime信息,存儲在gecko層。其實現原理是從/sys/class/power_supply/battery/目錄的文件中讀取信息,capacity文件存儲當前充電的進度(0~100),status存儲充電狀態(Charging或者Full),charging_source存儲充電的源(0:BATTERY_NOT_CHARGING,1:BATTERY_CHARGING_USB,2:BATTERY_CHARGING_AC)。
在js層BatteryManager 有4個只讀屬性:charging,chargingTime,dischargingTime,level,其對應的C實現見BatteryManagerBinding.cpp (objdir-gecko\dom\bindings)
|
對應的實現函數爲get_charging,get_chargingTime,get_dischargingTime,get_level;而後分別調用mozilla::dom::battery::BatteryManager中的對應函數,直接讀取mLevel,mCharging,mRemainingTime的值;當爲充電狀態的時候mRemainingTime的值做爲chargingTime值,不然做爲dischargingTime的值。
回調註冊流程跟其餘的public mozilla::dom::EventTarget中的on事件註冊流程相同,經過
IMPL_EVENT_HANDLER(chargingchange) IMPL_EVENT_HANDLER(chargingtimechange) IMPL_EVENT_HANDLER(dischargingtimechange) IMPL_EVENT_HANDLER(levelchange) |
staticbool set_onchargingchange(JSContext* cx, JS::Handle<JSObject*> obj, mozilla::dom::battery::BatteryManager* self, JSJitSetterCallArgs args) { nsRefPtr<EventHandlerNonNull> arg0; if(args[0].isObject()){ {// Scope for tempRoot JS::Rooted<JSObject*> tempRoot(cx,&args[0].toObject()); arg0 =new EventHandlerNonNull(tempRoot, mozilla::dom::GetIncumbentGlobal()); }
}else{ arg0 =nullptr; } self->SetOnchargingchange(Constify(arg0));
returntrue; } |
最終直接向BatteryManager的事件處理管理器中註冊回調函數,此處細節能夠參考《gecko消息機制分析.docx》文檔的3.5.1節。
當狀態改變時,調用流程以下:
#0 mozilla::dom::battery::BatteryManager::Notify (this=0xa994dc00, aBatteryInfo=...) at ../../../gecko/dom/battery/BatteryManager.cpp:104 #1 0xb4f59f7a in Broadcast (aParam=..., this=0xb6a35398) at ../dist/include/mozilla/Observer.h:67 #2 BroadcastInformation (aInfo=..., this=0xb64162b0<mozilla::hal::sBatteryObservers>) at ../../gecko/hal/Hal.cpp:227 #3 BroadcastCachedInformation ( this=0xb64162b0<mozilla::hal::sBatteryObservers>) at ../../gecko/hal/Hal.cpp:259 #4 mozilla::hal::NotifyBatteryChange (aInfo=...) at ../../gecko/hal/Hal.cpp:368 #5 0xb4f5bf76 in mozilla::hal_impl::(anonymous namespace)::BatteryUpdater::Run (this=<optimized out>) at ../../gecko/hal/gonk/GonkHal.cpp:290 |
109 DispatchTrustedEvent(LEVELCHANGE_EVENT_NAME); (gdb) p previousLevel $3 =0.95999999999999996 (gdb) p mLevel $4 =0.96999999999999997 |
通過DispatchTrustedEvent後會最終回調到註冊的JS代碼中。
地理位置 API 容許用戶向 Web 應用程序提供他們的位置。出於隱私考慮,報告地理位置前會先請求用戶許可。地理位置 API 經過navigator.geolocation提供。若是該對象存在,那麼地理位置服務可用。
if("geolocation" in navigator){ /* 地理位置服務可用 */ }else{ /* 地理位置服務不可用 */ } |
您能夠調用getCurrentPosition()函數獲取用戶當前定位位置。這會異步地請求獲取用戶位置,並查詢定位硬件來獲取最新信息。當定位被肯定後,定義的回調函數就會被執行。您能夠選擇性地提供第二個回調函數,當有錯誤時會被執行。第三個參數也是可選的,您能夠經過該對象參數設定最長可接受的定位返回時間、等待請求的時間和是否獲取高精度定位。
注意:默認狀況下,getCurrentPosition()會盡快返回一個低精度結果,這在您不關心準確度只關心快速獲取結果的狀況下頗有用。有 GPS 的設備可能須要一分鐘或更久來獲取 GPS 定位,在這種狀況下getCurrentPosition()會返回低精度數據(基於 IP 的定位或 Wi-Fi 定位)。
navigator.geolocation.getCurrentPosition(function(position){ do_something(position.coords.latitude, position.coords.longitude); }); |
上述示例中,當獲取位置後 do_something() 函數會被執行。
您能夠設定一個回調函數來響應定位數據發生的變動(設備發生了移動,或獲取到了更高精度的地理位置信息)。您能夠經過watchPosition()函數實現該功能。它與getCurrentPosition()接受相同的參數,但回調函數會被調用屢次。錯誤回調函數與getCurrentPosition()中同樣是可選的,也會被屢次調用。
注意:您能夠直接調用watchPosition()函數,不須要先調用getCurrentPosition()函數。
var watchID = navigator.geolocation.watchPosition(function(position){ do_something(position.coords.latitude, position.coords.longitude); }); |
watchPosition()函數會返回一個 ID,惟一地標記該位置監視器。您能夠將這個 ID 傳給clearWatch()函數來中止監視用戶位置。
navigator.geolocation.clearWatch(watchID); |
getCurrentPosition()和watchPosition()都接受一個成功回調、一個可選的失敗回調和一個可選的 PositionOptions 對象。
對watchPosition的調用相似於這樣:
function geo_success(position){ do_something(position.coords.latitude, position.coords.longitude); }
function geo_error(){ alert("Sorry, no position available."); }
var geo_options ={ enableHighAccuracy:true, maximumAge :30000, timeout :27000 };
var wpid = navigator.geolocation.watchPosition(geo_success, geo_error, geo_options); |
watchPosition 實際使用示例: http://www.thedotproduct.org/experiments/geo/
用戶的位置由一個包含 Coordinates 對象的 Position 對象描述。
getCurrentPosition() 或 watchPosition() 的錯誤回調函數以 PositionError 爲第一個參數。
function errorCallback(error){ alert('ERROR('+ error.code +'): '+ error.message); }; |
function geoFindMe(){ var output = document.getElementById("out");
if(!navigator.geolocation){ output.innerHTML ="<p><您的瀏覽器不支持地理位置</p>"; return; }
function success(position){ var latitude = position.coords.latitude; var longitude = position.coords.longitude;
output.innerHTML ='<p><Latitude is '+ latitude + '° Longitude is ' + longitude + '°</p>';
var img =new Image(); img.src ="http://maps.googleapis.com/maps/api/staticmap?center="+ latitude +","+ longitude +"&zoom=13&size=300x300&sensor=false";
output.appendChild(img); };
function error(){ output.innerHTML ="沒法獲取您的位置"; };
output.innerHTML ="<p><Locating…</p>";
navigator.geolocation.getCurrentPosition(success, error); } |
全部 addons.mozilla.org 上須要使用地理位置的插件必須在使用 API 前顯式地請求權限。用戶的響應將會存儲在 pref 參數指定的偏好設置中。callback 參數指定的函數會被調用幷包含一個表明用戶響應的 boolean 參數。若是爲 true,表明插件能夠訪問地理位置數據。
function prompt(window, pref, message, callback){ let branch = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch);
if(branch.getPrefType(pref)=== branch.PREF_STRING){ switch(branch.getCharPref(pref)){ case"always": return callback(true); case"never": return callback(false); } }
let done =false;
function remember(value, result){ return function(){ done =true; branch.setCharPref(pref, value); callback(result); } }
let self = window.PopupNotifications.show( window.gBrowser.selectedBrowser, "geolocation", message, "geo-notification-icon", { label:"Share Location", accessKey:"S", callback: function(notification){ done =true; callback(true); } },[ { label:"Always Share", accessKey:"A", callback: remember("always",true) }, { label:"Never Share", accessKey:"N", callback: remember("never",false) } ],{ eventCallback: function(event){ if(event ==="dismissed"){ if(!done) callback(false); done =true; window.PopupNotifications.remove(self); } }, persistWhileVisible:true }); }
prompt(window, "extensions.foo-addon.allowGeolocation", "Foo Add-on wants to know your location.", function callback(allowed){ alert(allowed);}); |
Geolocation* Navigator::GetGeolocation(ErrorResult& aRv) { if(mGeolocation){ return mGeolocation; }
if(!mWindow ||!mWindow->GetOuterWindow()||!mWindow->GetDocShell()){ aRv.Throw(NS_ERROR_FAILURE); returnnullptr; }
mGeolocation =new Geolocation(); if(NS_FAILED(mGeolocation->Init(mWindow->GetOuterWindow()))){ mGeolocation =nullptr; aRv.Throw(NS_ERROR_FAILURE); returnnullptr; }
return mGeolocation; }
|
navigator.mozAlarms是一個MozAlarmsManager對象。MozAlarmsManager容許在指定的時間點彈出一個notifications通知或者啓動一個app。
接口定義以下:
interface MozAlarmsManager { DOMRequest getAll(); DOMRequest add(Date date, DOMString respectTimezone, optional object data); void remove(unsignedlong id); }; |
MozAlarmsManager.getAll():獲取當前全部設定的alarms列表。
MozAlarmsManager.add():設定一個新的alarm
MozAlarmsManager.remove():刪除一個已經存在的alarm
獲取當前全部設定的alarms列表。
var request = navigator.mozAlarms.getAll();
request.onsuccess = function (){ console.log('operation successful:'+this.result.length +'alarms pending');
this.result.forEach(function (alarm){ console.log(alarm.id +' : '+ alarm.date.toString()+' : '+ alarm.respectTimezone); }); }
request.onerror = function (){ console.log('operation failed: '+this.error); } |
navigator.mozAlarms.getAll();返回一個DOMRequest對象,能夠處理success和error消息。this.result是一個匿名的數組對象,每個對象含有下面的屬性:
id:表明alarm的id的一個數字。
Date:一個Date類型的對象,表明了設定的alarm的時間。
respectTimezone:一個字串,表示是否關心時區,值爲「ignoreTimezone」或者「honorTimezone」
data:一個JS對象,存儲了alarm的相關信息。
查看Navigator.cpp (gecko\dom\base)及NavigatorBinding.cpp (objdir-gecko\dom\bindings)都沒有發現alarm相關的實現。查看omni\components\components.manifest中有以下定義
component {fea1e884-9b05-11e1-9b64-87a7016c3860} AlarmsManager.js contract @mozilla.org/alarmsManager;1{fea1e884-9b05-11e1-9b64-87a7016c3860} category JavaScript-navigator-property mozAlarms @mozilla.org/alarmsManager;1 |
可知,MozAlarms的實如今AlarmsManager.js中。
全部的JS代碼實現的navigator的對象都是在第一個對象被訪問時會把全部的信息都加載進來,可是此時只加載被訪問的JS對象的代碼,方便後面的訪問。
Line 24: category JavaScript-navigator-property mozPermissionSettings @mozilla.org/permissionSettings;1 Line 29: category JavaScript-navigator-property mozAlarms @mozilla.org/alarmsManager;1 Line 73: category JavaScript-navigator-property mozWifiManager @mozilla.org/wifimanager;1 Line 90: category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1 Line 203: category JavaScript-navigator-property mozApps @mozilla.org/webapps;1 Line 228: category JavaScript-navigator-property mozId @mozilla.org/dom/identity;1 Line 248: category JavaScript-navigator-property mozTCPSocket @mozilla.org/tcp-socket;1 Line 255: category JavaScript-navigator-property mozPay @mozilla.org/payment/content-helper;1 Line 262: category JavaScript-navigator-property mozKeyboard @mozilla.org/b2g-keyboard;1 |
即上面9個對象中的任何一個被訪問到就會所有加載對應信息,在我們7715手機上debug的結果是最早訪問到wifimanager對象,其調用堆棧以下:
Breakpoint 1, nsScriptNameSpaceManager::OperateCategoryEntryHash ( this=this@entry=0xb24a7be0, aCategoryManager=aCategoryManager@entry= 0xb6a64ec0, aCategory=aCategory@entry=0xb5ef8b80"JavaScript-navigator-property", aEntry=0xb1311d60, aRemove=aRemove@entry=false) at ../../../gecko/dom/base/nsScriptNameSpaceManager.cpp:625 625 type = nsGlobalNameStruct::eTypeNavigatorProperty; (gdb) bt #0 nsScriptNameSpaceManager::OperateCategoryEntryHash ( this=this@entry=0xb24a7be0, aCategoryManager=aCategoryManager@entry=0xb6a64ec0, aCategory=aCategory@entry=0xb5ef8b80"JavaScript-navigator-property", aEntry=0xb1311d60, aRemove=aRemove@entry=false) at ../../../gecko/dom/base/nsScriptNameSpaceManager.cpp:625 #1 0xb53b4ad6 in nsScriptNameSpaceManager::AddCategoryEntryToHash ( this=this@entry=0xb24a7be0, aCategoryManager=aCategoryManager@entry=0xb6a64ec0, aCategory=aCategory@entry=0xb5ef8b80"JavaScript-navigator-property", aEntry=<optimized out>) at ../../../gecko/dom/base/nsScriptNameSpaceManager.cpp:749 #2 0xb53b4b18 in nsScriptNameSpaceManager::FillHash ( this=this@entry=0xb24a7be0, aCategoryManager=0xb6a64ec0, aCategory=0xb5ef8b80"JavaScript-navigator-property") at ../../../gecko/dom/base/nsScriptNameSpaceManager.cpp:198 #3 0xb53b4c2a in nsScriptNameSpaceManager::Init (this=0xb24a7be0) at ../../../gecko/dom/base/nsScriptNameSpaceManager.cpp:374 #4 0xb53960d6 in mozilla::dom::GetNameSpaceManager () at ../../../gecko/dom/base/nsJSEnvironment.cpp:3120 #5 0xb53a614e in nsDOMClassInfo::Init () at ../../../gecko/dom/base/nsDOMClassInfo.cpp:895 #6 0xb53a819c in NS_GetDOMClassInfoInstance ( ---Type <return> to continue,or q <return> to quit--- aID=eDOMClassInfo_ChromeMessageBroadcaster_id) at ../../../gecko/dom/base/nsDOMClassInfo.cpp:1742 #7 0xb54f0a84 in QueryInterface (aInstancePtr=0xbea5326c, aIID=..., this=0xb24a7b20) at ../../../../gecko/content/base/src/nsFrameMessageManager.cpp:126 #8 nsFrameMessageManager::QueryInterface (this=0xb24a7b20, aIID=..., aInstancePtr=0xbea5326c) at ../../../../gecko/content/base/src/nsFrameMessageManager.cpp:90 #9 0xb4d514f4 in nsQueryInterface::operator() (this=this@entry=0xbea53264, aIID=..., answer=answer@entry=0xbea5326c) at ../../../gecko/xpcom/glue/nsCOMPtr.cpp:14 |
(gdb) call DumpJSStack() 0 WifiWorker()["jar:file:///system/b2g/omni.ja!/components/WifiWorker.js":1642] this=[object Object] 1 anonymous(outer = null, iid ={c04f3102-1ce8-4d57-9c27-8aece9c2740a})["resource://gre/modules/XPCOMUtils.jsm":271] this=[object Object] |
其中會表示加載全部JS對象的關鍵代碼在下面的這個循環中:(nsScriptNameSpaceManager.cpp (gecko\dom\base)) ,其中aCategory的值就是"JavaScript-navigator-property",會循環獲取裏面的值AddCategoryEntryToHash。
nsresult nsScriptNameSpaceManager::FillHash(nsICategoryManager *aCategoryManager, constchar*aCategory) { nsCOMPtr<nsISimpleEnumerator> e; nsresult rv = aCategoryManager->EnumerateCategory(aCategory, getter_AddRefs(e)); NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsISupports> entry; while(NS_SUCCEEDED(e->GetNext(getter_AddRefs(entry)))){ rv = AddCategoryEntryToHash(aCategoryManager, aCategory, entry); if(NS_FAILED(rv)){ return rv; } }
return NS_OK; } |
當JS對象被訪問時纔會執行對象的構造函數,而且會默認執行init函數,其調用堆棧以下:
#0 mozilla::dom::ConstructJSImplementation (aCx=aCx@entry=0xaf671ed0, aContractId=<optimized out>, aGlobal=..., aObject=..., aObject@entry=..., aRv=...) at ../../../gecko/dom/bindings/BindingUtils.cpp:2028 #1 0xb526a05a in ConstructNavigatorObjectHelper (aRv=..., global=..., cx=0xaf671ed0) at SettingsManagerBinding.cpp:1015 #2 mozilla::dom::SettingsManagerBinding::ConstructNavigatorObject ( aCx=0xaf671ed0, aObj=...) at SettingsManagerBinding.cpp:1032 #3 0xb53a0af0 in mozilla::dom::Navigator::DoNewResolve ( this=this@entry=0xab950c10, aCx=aCx@entry=0xaf671ed0, aObject=aObject@entry=..., aId=..., aId@entry=..., aDesc=...) at ../../../gecko/dom/base/Navigator.cpp:1886 #4 0xb52042ae in mozilla::dom::NavigatorBinding::_newResolve (cx=0xaf671ed0, obj=..., id=..., flags=<optimized out>, objp=...) at NavigatorBinding.cpp:2311 #5 0xb5c2604a in CallResolveOp (propp=..., objp=..., id=..., obj=..., cx=<optimized out>, recursedp=<synthetic pointer>, flags=<optimized out>) at ../../../gecko/js/src/jsobj.cpp:3921 #6 LookupOwnPropertyWithFlagsInline<(js::AllowGC)1> ( donep=<synthetic pointer>, propp=..., objp=..., id=..., obj=..., flags=<optimized out>, cx=<optimized out>) at ../../../gecko/js/src/jsobj.cpp:4011 #7 LookupPropertyWithFlagsInline<(js::AllowGC)1> (propp=..., objp=..., flags=65535, id=..., obj=..., cx=0xaf671ed0) ---Type <return> to continue,or q <return> to quit--- at ../../../gecko/js/src/jsobj.cpp:4072 #8 js::baseops::LookupProperty<(js::AllowGC)1> (cx=0xaf671ed0, obj=..., id=..., objp=..., propp=...) at ../../../gecko/js/src/jsobj.cpp:4113 |
|
再次回到咱們當前關注的MozAlarms,當第一次被訪問時,會加載對應的實現文件AlarmsManager.js,而且會執行對應的init函數。
AlarmsManager.js (gecko\dom\alarm) |
const{ classes: Cc, interfaces: Ci, utils: Cu, results: Cr }= Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
const ALARMSMANAGER_CONTRACTID ="@mozilla.org/alarmsManager;1"; const ALARMSMANAGER_CID = Components.ID("{fea1e884-9b05-11e1-9b64-87a7016c3860}"); const nsIDOMMozAlarmsManager = Ci.nsIDOMMozAlarmsManager; const nsIClassInfo = Ci.nsIClassInfo;
function AlarmsManager() { debug("Constructor"); } |
// nsIDOMGlobalPropertyInitializer implementation init: function init(aWindow){ debug("init()");
// Set navigator.mozAlarms to null. if(!Services.prefs.getBoolPref("dom.mozAlarms.enabled")){ return null; }
// Only pages with perm set can use the alarms. let principal = aWindow.document.nodePrincipal; let perm = Services.perms.testExactPermissionFromPrincipal(principal,"alarms"); if(perm != Ci.nsIPermissionManager.ALLOW_ACTION){ return null; }
// SystemPrincipal documents do not have any origin. // Reject them for now. if(!principal.URI){ return null; }
this._cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"] .getService(Ci.nsISyncMessageSender);
// Add the valid messages to be listened. this.initDOMRequestHelper(aWindow,["AlarmsManager:Add:Return:OK", "AlarmsManager:Add:Return:KO", "AlarmsManager:GetAll:Return:OK", "AlarmsManager:GetAll:Return:KO"]);
// Get the manifest URL if this is an installed app let appsService = Cc["@mozilla.org/AppsService;1"] .getService(Ci.nsIAppsService); this._pageURL = principal.URI.spec; this._manifestURL = appsService.getManifestURLByLocalId(principal.appId); this._window = aWindow; },
// Called from DOMRequestIpcHelper. uninit: function uninit(){ debug("uninit()"); }, } |
因而可知會加載Services.jsm,DOMRequestHelper.jsm,而且定義當前的component對應的CID和IID值,加載CPMM和appsService,推測後續是須要進行IPC消息發送的。
navigator.mozAlarms.getAll()的調用是在客戶端進程中的,實現流程以下(AlarmsManager.js):
getAll: function getAll(){ debug("getAll()");
let request =this.createRequest(); this._cpmm.sendAsyncMessage( "AlarmsManager:GetAll", { requestId:this.getRequestId(request), manifestURL:this._manifestURL } ); return request; }, |
DOMRequestHelper.jsm (gecko\dom\base) 9431 2014/12/1: createRequest: function(){ return Services.DOMRequest.createRequest(this._window); }, |
DOMRequest.cpp (gecko\dom\base) NS_IMETHODIMP DOMRequestService::CreateRequest(nsIDOMWindow* aWindow, nsIDOMDOMRequest** aRequest) { nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(aWindow)); NS_ENSURE_STATE(win); NS_ADDREF(*aRequest =new DOMRequest(win));
return NS_OK; } |
能夠看出this.createRequest()返回的是DOMRequest對象。
下面接着分析this.getRequestId(request)的做用:
DOMRequestHelper.jsm (gecko\dom\base) getRequestId: function(aRequest){ if(!this._requests){ this._requests ={}; }
let id ="id"+this._getRandomId(); this._requests[id]= aRequest; return id; }, |
能夠看出其做用是將new出來的DOMRequest保存在this對象裏面,並轉換爲一個ID值,這樣作的目的是爲了在IPC通訊回來後能夠找到對應的DOMRequest,而且將result值設置好,從而在success和error消息中能夠訪問到返回的數據。
最後this._cpmm.sendAsyncMessage的目的是發送IPC消息,具體流程請參考《gecko消息機制分析.docx》,發送過去的"AlarmsManager:GetAll",並經過_manifestURL標識客戶端的進程信息。
在B2G進程中,處理"AlarmsManager:GetAll"消息的代碼實如今AlarmService.jsm (gecko\dom\alarm)文件中的receiveMessage中,以下:
receiveMessage: function receiveMessage(aMessage){ debug("receiveMessage(): "+ aMessage.name); let json = aMessage.json;
// To prevent the hacked child process from sending commands to parent // to schedule alarms, we need to check its permission and manifest URL. if(this._messages.indexOf(aMessage.name)!=-1){ if(!aMessage.target.assertPermission("alarms")){ debug("Got message from a child process with no 'alarms' permission."); return null; } if(!aMessage.target.assertContainApp(json.manifestURL)){ debug("Got message from a child process containing illegal manifest URL."); return null; } }
let mm = aMessage.target.QueryInterface(Ci.nsIMessageSender); switch(aMessage.name){ case"AlarmsManager:GetAll": this._db.getAll( json.manifestURL, function getAllSuccessCb(aAlarms){ debug("Callback after getting alarms from database: "+ JSON.stringify(aAlarms)); this._sendAsyncMessage(mm,"GetAll",true, json.requestId, aAlarms); }.bind(this), function getAllErrorCb(aErrorMsg){ this._sendAsyncMessage(mm,"GetAll",false, json.requestId, aErrorMsg); }.bind(this) ); break;
default: throw Components.results.NS_ERROR_NOT_IMPLEMENTED; break; } }, |
首選獲得客戶端傳說過來的JSON數據,即{ requestId: this.getRequestId(request), manifestURL: this._manifestURL },經過manifestURL檢查是否有獲取alarm的權限(這個權限是在app的manifest中申請的,在B2G加載應用信息的時候保持在進程中的)。
而後經過數據庫操做_db.getAll()獲取alarms的信息,最後經過_sendAsyncMessage將信息經過IPC消息傳給客戶端。
下面分析_db.getAll()和_sendAsyncMessage的細節,先看_db.getAll(),全部alarm的數據庫都在AlarmDB.jsm (gecko\dom\alarm)中實現的。
/** * @param aManifestURL * The manifest URL of the app that alarms belong to. * If null, directly return all alarms; otherwise, * only return the alarms that belong to this app. * @param aSuccessCb * Callback function to invoke with result array. * @param aErrorCb [optional] * Callback function to invoke when there was an error. */ getAll: function getAll(aManifestURL, aSuccessCb, aErrorCb){ debug("getAll()");
this.newTxn( "readonly", ALARMSTORE_NAME, function txnCb(aTxn, aStore){ if(!aTxn.result){ aTxn.result =[]; }
let index = aStore.index("manifestURL"); index.mozGetAll(aManifestURL).onsuccess = function setTxnResult(aEvent){ aTxn.result = aEvent.target.result; debug("Request successful. Record count: "+ aTxn.result.length); }; }, aSuccessCb, aErrorCb ); } |
具體數據庫處理細節這裏就不分析了,咱們看存儲在數據庫中的項,即返回的object所含有的屬性值:
upgradeSchema: function upgradeSchema(aTransaction, aDb, aOldVersion, aNewVersion){ debug("upgradeSchema()");
let objectStore = aDb.createObjectStore(ALARMSTORE_NAME,{ keyPath:"id", autoIncrement:true});
objectStore.createIndex("date", "date", { unique:false}); objectStore.createIndex("ignoreTimezone","ignoreTimezone",{ unique:false}); objectStore.createIndex("timezoneOffset","timezoneOffset",{ unique:false}); objectStore.createIndex("data", "data", { unique:false}); objectStore.createIndex("pageURL", "pageURL", { unique:false}); objectStore.createIndex("manifestURL", "manifestURL", { unique:false});
debug("Created object stores and indexes"); }, |
其中紅色的4個值是與alarm信息相關的,也是須要傳回去的。
下面繼續分析_sendAsyncMessage:
_sendAsyncMessage: function _sendAsyncMessage(aMessageManager, aMessageName, aSuccess, aRequestId, aData){ debug("_sendAsyncMessage()");
if(!aMessageManager){ debug("Invalid message manager: null"); throw Components.results.NS_ERROR_FAILURE; }
let json = null; switch(aMessageName) { case"GetAll": json = aSuccess ? {requestId: aRequestId, alarms: aData}: { requestId: aRequestId, errorMsg: aData }; break;
default: throw Components.results.NS_ERROR_NOT_IMPLEMENTED; break; }
aMessageManager.sendAsyncMessage("AlarmsManager:" + aMessageName + ":Return:" +(aSuccess ? "OK" : "KO"), json); }, |
能夠看出若是成功則返回消息AlarmsManager:GetAll:Return:OK,並將返回的數據做爲alarms,出錯則返回AlarmsManager:GetAll:Return:KO.
在客戶端AlarmsManager.js (gecko\dom\alarm)中的receiveMessage處理返回的消息:
receiveMessage: function receiveMessage(aMessage){ debug("receiveMessage(): "+ aMessage.name);
let json = aMessage.json; let request =this.getRequest(json.requestId);
if(!request){ debug("No request stored! "+ json.requestId); return; }
switch(aMessage.name){ case"AlarmsManager:GetAll:Return:OK": // We don't need to expose everything to the web content. let alarms =[]; json.alarms.forEach(function trimAlarmInfo(aAlarm){ let alarm ={ "id": aAlarm.id, "date": aAlarm.date, "respectTimezone": aAlarm.ignoreTimezone ? "ignoreTimezone" : "honorTimezone", "data": aAlarm.data }; alarms.push(alarm); }); Services.DOMRequest.fireSuccess(request, Cu.cloneInto(alarms,this._window)); break;
case"AlarmsManager:GetAll:Return:KO": Services.DOMRequest.fireError(request, json.errorMsg); break;
default: debug("Wrong message: "+ aMessage.name); break; } this.removeRequest(json.requestId); }, |
先經過返回的ID來getRequest(json.requestId),而後將返回的數組中的數據轉換爲alarms數組,最後經過fireSuccess或者fireError回調到onsucess或者onerror。
綜上所述:全部的alarms信息到保存在一個數據庫中,只有B2G進程纔有權限讀取數據庫;客戶端須要查詢本身應用的alarms的時候,經過發送IPC消息到B2G進行查詢,B2G進程先檢查權限,而後從數據庫中查詢發請求的app的全部alarms返回回來。
對應的時序圖以下:
|
另外兩個接口add和remove的處理邏輯相似,不一樣只是發送的消息和代入的參數不一樣,轉換爲數據庫操做的時候分別對應add和remove操做。
navigator.mozApps是一個Apps對象,能夠經過apps來安裝管理和控制一個open web app。
Apps對象含有的屬性:DOMApplicationsRegistry.mgmt;方法有:DOMApplicationsRegistry.checkInstalled(),DOMApplicationsRegistry.install(),DOMApplicationsRegistry.getSelf(),DOMApplicationsRegistry.getInstalled()。
DOMApplicationsManager類型的對象,能夠容許用戶在界面上(dashboards??)進行app的管理和啓動。
var mgmt = navigator.mozApps.mgmt; |
當接收到install事件的時候被觸發。
當接收到uninstall事件的時候被觸發。
當接收到enablestatechange事件的時候被觸發。
返回全部的apps。
獲取指定的app的信息,經過這個接口能夠判斷app是否被安裝了,傳入的url爲app的manifest。
var request = window.navigator.mozApps.checkInstalled(url); |
返回一個DOMRequest對象,DOMRequest.result屬性是一個DOMApplication對象,描述了被安裝的app的信息,若是應用沒有被安裝,則爲null。
{ manifest:{ name:"Add-on Builder", default_locale:"en", installs_allowed_from:[ "https://apps-preview-dev.example.com/", "https://apps-preview.example.com/" ], description:"Add-on Builder makes it easy to write, build and test Firefox extensions using common web technologies.", version:"0.9.16.1", developer:{ url:"https://builder.addons.mozilla.org/", name:"Mozilla Flightdeck Team" } }, origin:"https://builder-addons-dev.example.com", installTime:1321986882773, installOrigin:"https://apps-preview-dev.example.com", receipts:["h0dHBzOi8v (most of receipt removed here) Tg2ODtkUp"] } |
var request = window.navigator.mozApps.checkInstalled("http://example.com/manifest.webapp"); request.onerror = function(e){ alert("Error calling checkInstalled: "+ request.error.name); }; request.onsuccess = function(e){ if(request.result){ console.log("App is installed!"); } else{ console.log("App is not installed!"); } }; |
若是請求獲取的app的domain與當前的app不是domain則在函數調用後當即返回以訛NS_ERROR_DOM_BAD_URI的錯誤。
觸發安裝一個app,在安裝的過程當中,應用須要進行驗證而且彈出提示框讓用戶進行確認。若是這個app以前從相同的domain中安裝過,調用install()會覆蓋原來已經存在的安裝數據。
var request = window.navigator.mozApps.install(manifestUrl); request.onsuccess = function (){ // Save the App object that is returned var appRecord =this.result; alert('Installation successful!'); }; request.onerror = function (){ // Display the error information from the DOMError object alert('Install failed, error: '+this.error.name); };
|
獲取當前應用的app信息。返回一個DOMRequest對象,若是成功DOMRequest.result屬性是一個DOMApplication對象,描述了被安裝的app的信息,失敗則爲null。
var request = window.navigator.mozApps.getSelf(); request.onsuccess = function(){ if(request.result){ // Pull the name of the app out of the App object alert("Name of current app: "+ request.result.manifest.name); }else{ alert("Called from outside of an app"); } }; request.onerror = function(){ // Display error name from the DOMError object alert("Error: "+ request.error.name); }; |
獲取從當前的orgin安裝的app列表,例如若是在Marketplace中調用這個接口則返回全部從Marketplace中安裝的app列表。
var request = window.navigator.mozApps.getInstalled(); request.onerror = function(e){ alert("Error calling getInstalled: "+ request.error.name); }; request.onsuccess = function(e){ alert("Success, number of apps: "+ request.result.length); var appsRecord = request.result; }; |
若是success則,request.result爲app對象的數組。
查看Navigator.cpp (gecko\dom\base)及NavigatorBinding.cpp (objdir-gecko\dom\bindings)都沒有發現mozApps相關的實現。查看omni\components\ components.manifest中有以下定義。
component {fff440b3-fae2-45c1-bf03-3b5a2e432270} Webapps.js contract @mozilla.org/webapps;1{fff440b3-fae2-45c1-bf03-3b5a2e432270} category JavaScript-navigator-property mozApps @mozilla.org/webapps;1 |
可知,mozApps的實如今Webapps.js中。咱們查看Webapps.js,發現裏面有多個對象定義,查找CID爲fff440b3-fae2-45c1-bf03-3b5a2e432270的對象爲WebappsRegistry,能夠肯定mozApps對應在gecko的實現爲WebappsRegistry對象。
function WebappsRegistry(){ }
WebappsRegistry.prototype={ __proto__: DOMRequestIpcHelper.prototype,
classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver, Ci.mozIDOMApplicationRegistry, Ci.mozIDOMApplicationRegistry2, Ci.nsIDOMGlobalPropertyInitializer]),
classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"), contractID:"@mozilla.org/webapps;1", interfaces:[Ci.mozIDOMApplicationRegistry, Ci.mozIDOMApplicationRegistry2], flags: Ci.nsIClassInfo.DOM_OBJECT, classDescription:"Webapps Registry"}) } |
// nsIDOMGlobalPropertyInitializer implementation init: function(aWindow){ this.initDOMRequestHelper(aWindow,"Webapps:Install:Return:OK");
let util =this._window.QueryInterface(Ci.nsIInterfaceRequestor) .getInterface(Ci.nsIDOMWindowUtils); this._id = util.outerWindowID; cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { messages:["Webapps:Install:Return:OK"]});
let principal = aWindow.document.nodePrincipal; let perm = Services.perms .testExactPermissionFromPrincipal(principal,"webapps-manage");
// Only pages with the webapps-manage permission set can get access to // the mgmt object. this.hasMgmtPrivilege = perm == Ci.nsIPermissionManager.ALLOW_ACTION; },
classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.nsIObserver, Ci.mozIDOMApplicationRegistry, Ci.mozIDOMApplicationRegistry2, Ci.nsIDOMGlobalPropertyInitializer]),
classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"), contractID:"@mozilla.org/webapps;1", interfaces:[Ci.mozIDOMApplicationRegistry, Ci.mozIDOMApplicationRegistry2], flags: Ci.nsIClassInfo.DOM_OBJECT, classDescription:"Webapps Registry"}) }
|
能夠看出app向B2G註冊了"Webapps:Install:Return:OK,對應的消息是在Webapps.jsm (gecko\dom\apps\src)中處理的,當B2G進程發出消息,會通知到B2G的Webapps.jsm,而後經過IPC消息轉發到app的Webapps.js,WebappsRegistry的receiveMessage會進行處理。
Mgmt()函數返回一個WebappsApplicationMgmt對象。
get mgmt(){ if(!this.hasMgmtPrivilege){ return null; }
if(!this._mgmt) this._mgmt =new WebappsApplicationMgmt(this._window); returnthis._mgmt; }, |
WebappsApplicationMgmt的構造函數中作了以下事情:
function WebappsApplicationMgmt(aWindow){ this.initDOMRequestHelper(aWindow,["Webapps:GetAll:Return:OK", "Webapps:GetAll:Return:KO", "Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Broadcast:Return:OK", "Webapps:Uninstall:Return:KO", "Webapps:Install:Return:OK", "Webapps:GetNotInstalled:Return:OK"]);
cpmm.sendAsyncMessage("Webapps:RegisterForMessages", { messages:["Webapps:Install:Return:OK", "Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Broadcast:Return:OK"] } );
this._oninstall = null; this._onuninstall = null; } |
能夠看出app向B2G註冊了"Webapps:Install:Return:OK"和"Webapps:Uninstall:Return:OK"及"Webapps:Uninstall:Broadcast:Return:OK",對應的消息是在Webapps.jsm (gecko\dom\apps\src)中處理的,當B2G進程發出這些消息,會通知到B2G的Webapps.jsm,而後經過IPC消息轉發到app的Webapps.js,WebappsRegistry的receiveMessage會進行處理,同時receiveMessage還會處理initDOMRequestHelper中註冊的其餘消息。
checkInstalled: function(aManifestURL){ let manifestURL = Services.io.newURI(aManifestURL, null,this._window.document.baseURIObject); this._window.document.nodePrincipal.checkMayLoad(manifestURL,true,false);
let request =this.createRequest();
this.addMessageListeners("Webapps:CheckInstalled:Return:OK"); cpmm.sendAsyncMessage("Webapps:CheckInstalled",{ origin:this._getOrigin(this._window.location.href), manifestURL: manifestURL.spec, oid:this._id, requestID:this.getRequestId(request)}); return request; }, |
主要工做是經過CPMM發送了Webapps:CheckInstalled消息。這個消息是在Webapps.jsm (gecko\dom\apps\src)的receiveMessage中處理的
case"Webapps:CheckInstalled": this.checkInstalled(msg, mm); break; |
checkInstalled: function(aData, aMm){ aData.app = null; let tmp =[];
for(let appId in this.webapps){ if(this.webapps[appId].manifestURL == aData.manifestURL && this._isLaunchable(this.webapps[appId])){ aData.app= AppsUtils.cloneAppObject(this.webapps[appId]); tmp.push({ id: appId }); break; } }
this._readManifests(tmp).then((aResult)=>{ for(let i =0; i < aResult.length; i++){ aData.app.manifest= aResult[i].manifest; break; } aMm.sendAsyncMessage("Webapps:CheckInstalled:Return:OK", aData); }); }, |
從this.webapps[](即DOMApplicationRegistry. Webapps[])數組中查找與傳入的manifestURL相同的app,而後分別讀取app的manifest信息,將app信息和manifest信息都做爲返回參數aData的屬性傳回,並返回消息"Webapps:CheckInstalled:Return:OK"。
其中this.webapps[]的信息是在loadCurrentRegistry中初始化的。
// loads the current registry, that could be empty on first run. loadCurrentRegistry: function(){ returnAppsUtils.loadJSONAsync(this.appsFile).then((aData)=>{ if(!aData){ return; }
this.webapps = aData; let appDir = OS.Path.dirname(this.appsFile); for(let id in this.webapps){ let app =this.webapps[id]; if(!app){ deletethis.webapps[id]; continue; }
app.id = id;
// Make sure we have a localId if(app.localId === undefined){ app.localId =this._nextLocalId(); }
if(app.basePath === undefined){ app.basePath = appDir; }
// Default to removable apps. if(app.removable === undefined){ app.removable =true; }
// Default to a non privileged status. if(app.appStatus === undefined){ app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED; }
// Default to NO_APP_ID and not in browser. if(app.installerAppId === undefined){ app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID; } if(app.installerIsBrowser === undefined){ app.installerIsBrowser =false; }
// Default installState to "installed", and reset if we shutdown // during an update. if(app.installState === undefined || app.installState ==="updating"){ app.installState ="installed"; }
// Default storeId to "" and storeVersion to 0 if(this.webapps[id].storeId === undefined){ this.webapps[id].storeId =""; } if(this.webapps[id].storeVersion === undefined){ this.webapps[id].storeVersion =0; }
// Default role to "". if(this.webapps[id].role === undefined){ this.webapps[id].role =""; }
// At startup we can't be downloading, and the $TMP directory // will be empty so we can't just apply a staged update. app.downloading =false; app.readyToApplyDownload =false; } }); }, |
在我們7715手機上對應的文件是root@scx15_sp7715ga:/system/b2g/webapps # cat webapps.json,裏面的信息以下截圖,讀取後成爲一個object數組。
|
B2G返回的"Webapps:CheckInstalled:Return:OK"消息是在Webapps.js (gecko\dom\apps\src)的receiveMessage中處理的:
receiveMessage: function(aMessage){ let msg = aMessage.json; if(msg.oid !=this._id) return let req =this.getRequest(msg.requestID); if(!req) return; let app = msg.app; switch(aMessage.name){ case"Webapps:CheckInstalled:Return:OK": this.removeMessageListeners(aMessage.name); Services.DOMRequest.fireSuccess(req, msg.app); break;
} this.removeRequest(msg.requestID); }, |
直接將B2G返回的app信息回調給onsuccess.
DOMApplicationsRegistry.install的實現體在Webapps.js (gecko\dom\apps\src)中實現以下:
install: function(aURL, aParams){ let request =this.createRequest();
let uri =this._validateURL(aURL, request);
if(uri &&this._ensureForeground(request)){ this.addMessageListeners("Webapps:Install:Return:KO"); cpmm.sendAsyncMessage("Webapps:Install", this._prepareInstall(uri, request, aParams,false)); }
return request; }, |
能夠看出主要是經過CPMM向B2G發送了"Webapps:Install"消息,其中傳遞給B2G的數據是經過_prepareInstall來構造的,主要:
_prepareInstall: function(aURL, aRequest, aParams, isPackage){ let installURL =this._window.location.href; let requestID =this.getRequestId(aRequest); let receipts =(aParams && aParams.receipts && Array.isArray(aParams.receipts))? aParams.receipts :[]; let categories =(aParams && aParams.categories && Array.isArray(aParams.categories))? aParams.categories :[];
let principal =this._window.document.nodePrincipal;
return{app:{ installOrigin:this._getOrigin(installURL), origin:this._getOrigin(aURL), manifestURL: aURL, receipts: receipts, categories: categories },
from: installURL, oid:this._id, requestID: requestID, appId: principal.appId, isBrowser: principal.isInBrowserElement, isPackage: isPackage }; }, |
在B2G進程中處理"Webapps:Install"消息在Webapps.jsm文件的receiveMessage中:
case"Webapps:Install":{ this.doInstall(msg, mm); break; } |
// Downloads the manifest and run checks, then eventually triggers the // installation UI. doInstall: function doInstall(aData, aMm){ let app = aData.app;
let sendError = function sendError(aError){ aData.error = aError; aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData); Cu.reportError("Error installing app from: "+ app.installOrigin + ": "+ aError); }.bind(this);
if(app.receipts.length >0){ for(let receipt of app.receipts){ let error =this.isReceipt(receipt); if(error){ sendError(error); return; } } }
// Hosted apps can't be trusted or certified, so just check that the // manifest doesn't ask for those. function checkAppStatus(aManifest){ let manifestStatus = aManifest.type ||"web"; return manifestStatus ==="web"; }
let checkManifest =(function(){ if(!app.manifest){ sendError("MANIFEST_PARSE_ERROR"); returnfalse; }
// Disallow multiple hosted apps installations from the same origin for now. // We will remove this code after multiple apps per origin are supported (bug 778277). // This will also disallow reinstalls from the same origin for now. for(let id in this.webapps){ if(this.webapps[id].origin == app.origin && !this.webapps[id].packageHash && this._isLaunchable(this.webapps[id])){ sendError("MULTIPLE_APPS_PER_ORIGIN_FORBIDDEN"); returnfalse; } }
if(!AppsUtils.checkManifest(app.manifest, app)){ sendError("INVALID_MANIFEST"); returnfalse; }
if(!AppsUtils.checkInstallAllowed(app.manifest, app.installOrigin)){ sendError("INSTALL_FROM_DENIED"); returnfalse; }
if(!checkAppStatus(app.manifest)){ sendError("INVALID_SECURITY_LEVEL"); returnfalse; }
returntrue; }).bind(this);
let installApp =(function(){ app.manifestHash =this.computeManifestHash(app.manifest); // We allow bypassing the install confirmation process to facilitate // automation. let prefName ="dom.mozApps.auto_confirm_install"; if(Services.prefs.prefHasUserValue(prefName)&& Services.prefs.getBoolPref(prefName)){ this.confirmInstall(aData); }else{ Services.obs.notifyObservers(aMm,"webapps-ask-install", JSON.stringify(aData)); } }).bind(this);
// We may already have the manifest (e.g. AutoInstall), // in which case we don't need to load it. if(app.manifest){ if(checkManifest()){ installApp(); } return; }
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); xhr.open("GET", app.manifestURL,true); xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; xhr.channel.notificationCallbacks =this.createLoadContext(aData.appId, aData.isBrowser); xhr.responseType ="json";
xhr.addEventListener("load",(function(){ if(xhr.status ==200){ if(!AppsUtils.checkManifestContentType(app.installOrigin, app.origin, xhr.getResponseHeader("content-type"))){ sendError("INVALID_MANIFEST"); return; }
app.manifest = xhr.response; if(checkManifest()){ app.etag = xhr.getResponseHeader("Etag"); installApp(); } }else{ sendError("MANIFEST_URL_ERROR"); } }).bind(this),false); |