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); |