thanks to the author :
http://blog.csdn.net/hudongliang2006nb/article/details/5164704
2. BatteryManager
2.1. 介绍
BatteryManager提供了访问系统电源管理级别信息的方式。
navigator.getBattery返回一个battery promise,你可以用这个通过这个promise来与Battery Status API进行交互。
navigator.getBattery().then(function(battery){
console.log("Battery charging? "+(battery.charging ?"Yes":"No")); console.log("Battery level: "+ battery.level *100+"%"); console.log("Battery charging time: "+ battery.chargingTime +" seconds"); console.log("Battery discharging time: "+ battery.dischargingTime +" seconds");
battery.addEventListener('chargingchange', function(){ console.log("Battery charging? "+(battery.charging ?"Yes":"No")); });
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"); });
}); |
2.2. 原理分析
2.2.1. BatteryManager的初始化
当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)。
2.2.2. Js属性访问
在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的值。
2.2.3. 状态回调
回调注册流程跟其他的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代码中。
3. Geolocation
3.1. 介绍
地理位置 API 允许用户向 Web 应用程序提供他们的位置。出于隐私考虑,报告地理位置前会先请求用户许可。地理位置 API 通过navigator.geolocation提供。如果该对象存在,那么地理位置服务可用。
if("geolocation" in navigator){ /* 地理位置服务可用 */ }else{ /* 地理位置服务不可用 */ } |
3.1.1. 获取当前定位
您可以调用getCurrentPosition()函数获取用户当前定位位置。这会异步地请求获取用户位置,并查询定位硬件来获取最新信息。当定位被确定后,定义的回调函数就会被执行。您可以选择性地提供第二个回调函数,当有错误时会被执行。第三个参数也是可选的,您可以通过该对象参数设定最长可接受的定位返回时间、等待请求的时间和是否获取高精度定位。
注意:默认情况下,getCurrentPosition()会尽快返回一个低精度结果,这在您不关心准确度只关心快速获取结果的情况下很有用。有 GPS 的设备可能需要一分钟或更久来获取 GPS 定位,在这种情况下getCurrentPosition()会返回低精度数据(基于 IP 的定位或 Wi-Fi 定位)。
navigator.geolocation.getCurrentPosition(function(position){ do_something(position.coords.latitude, position.coords.longitude); }); |
上述示例中,当获取位置后 do_something() 函数会被执行。
3.1.2. 监视定位
您可以设定一个回调函数来响应定位数据发生的变更(设备发生了移动,或获取到了更高精度的地理位置信息)。您可以通过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); |
3.1.3. 调整返回结果
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/
3.1.4. 描述位置
用户的位置由一个包含 Coordinates 对象的 Position 对象描述。
3.1.5. 处理错误
getCurrentPosition() 或 watchPosition() 的错误回调函数以 PositionError 为第一个参数。
function errorCallback(error){ alert('ERROR('+ error.code +'): '+ error.message); }; |
3.2. 地理位置示例
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); } |
3.2.1. 授权请求
所有 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);}); |
3.3. 原理分析
3.3.1. 初始化
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; }
|
4. MozAlarmsManager
4.1. 介绍
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
4.1.1. getAll
获取当前所有设定的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的相关信息。
4.2. 原理分析
查看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中。
4.2.1. 初始化
所有的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消息发送的。
4.2.2. getAll
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操作。
5. mozApps
5.1. 介绍
navigator.mozApps是一个Apps对象,可以通过apps来安装管理和控制一个open web app。
Apps对象含有的属性:DOMApplicationsRegistry.mgmt;方法有:DOMApplicationsRegistry.checkInstalled(),DOMApplicationsRegistry.install(),DOMApplicationsRegistry.getSelf(),DOMApplicationsRegistry.getInstalled()。
5.1.1. DOMApplicationsRegistry.mgmt
DOMApplicationsManager类型的对象,可以允许用户在界面上(dashboards??)进行app的管理和启动。
var mgmt = navigator.mozApps.mgmt; |
5.1.1.1. DOMApplicationsManager.oninstall
当接收到install事件的时候被触发。
5.1.1.2. DOMApplicationsManager.onuninstall
当接收到uninstall事件的时候被触发。
5.1.1.3. OMApplicationsManager.onenablestatechange
当接收到enablestatechange事件的时候被触发。
5.1.1.4. DOMApplicationsManager.getAll
返回所有的apps。
5.1.2. DOMApplicationsRegistry.checkInstalled
获取指定的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的错误。
5.1.3. DOMApplicationsRegistry.install
触发安装一个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); };
|
5.1.4. DOMApplicationsRegistry.getSelf
获取当前应用的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); }; |
5.1.5. DOMApplicationsRegistry.getInstalled
获取从当前的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对象的数组。
5.2. 原理分析
查看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"}) } |
5.2.1. 初始化
// 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会进行处理。
5.2.2. DOMApplicationsRegistry.mgmt
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中注册的其他消息。
5.2.3. DOMApplicationsRegistry.checkInstalled
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.
5.2.4. DOMApplicationsRegistry.install
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);
xhr.addEventListener("error",(function(){ sendError("NETWORK_ERROR"); }).bind(this),false);
xhr.send(null); }, |
在doInstall中先下载manifest文件,允许权限检查并触发安装的UI界面。最终安装的实际动作是在confirmInstall中进行的。
confirmInstall:Task.async(function*(aData, aProfileDir, aInstallSuccessCallback){ debug("confirmInstall");
let origin = Services.io.newURI(aData.app.origin, null, null); let id =this._appIdForManifestURL(aData.app.manifestURL); let manifestURL = origin.resolve(aData.app.manifestURL); let localId =this.getAppLocalIdByManifestURL(manifestURL);
let isReinstall =false;
// Installing an application again is considered as an update. if(id){ isReinstall =true; let dir =this._getAppDir(id); try{ dir.remove(true); }catch(e){} }else{ id =this.makeAppId(); localId =this._nextLocalId(); }
let app =this._setupApp(aData, id);
let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest; this._writeManifestFile(id, aData.isPackage, jsonManifest);
debug("app.origin: "+ app.origin); let manifest =new ManifestHelper(jsonManifest, app.origin);
let appObject =this._cloneApp(aData, app, manifest, id, localId);
this.webapps[id]= appObject;
// For package apps, the permissions are not in the mini-manifest, so // don't update the permissions yet. if(!aData.isPackage){ if(supportUseCurrentProfile()){ PermissionsInstaller.installPermissions( { origin: appObject.origin, manifestURL: appObject.manifestURL, manifest: jsonManifest }, isReinstall, this.uninstall.bind(this, aData, aData.mm) ); }
this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin, this.webapps[id].manifestURL, jsonManifest, this.webapps[id].appStatus); }
for each (let prop in ["installState","downloadAvailable","downloading", "downloadSize","readyToApplyDownload"]){ aData.app[prop]= appObject[prop]; }
let dontNeedNetwork =false;
if(manifest.appcache_path){ this.queuedDownload[app.manifestURL]={ manifest: manifest, app: appObject, profileDir: aProfileDir } }elseif(manifest.package_path){ // If it is a local app then it must been installed from a local file // instead of web. #ifdef MOZ_ANDROID_SYNTHAPKS // In that case, we would already have the manifest, not just the update // manifest. dontNeedNetwork =!!aData.app.manifest; #else if(aData.app.localInstallPath){ dontNeedNetwork =true; jsonManifest.package_path ="file://"+ aData.app.localInstallPath; } #endif
// origin for install apps is meaningless here, since it's app:// and this // can't be used to resolve package paths. manifest =new ManifestHelper(jsonManifest, app.manifestURL);
this.queuedPackageDownload[app.manifestURL]={ manifest: manifest, app: appObject, callback: aInstallSuccessCallback }; }
// We notify about the successful installation via mgmt.oninstall and the // corresponging DOMRequest.onsuccess event as soon as the app is properly // saved in the registry. yield this._saveApps();
this.broadcastMessage("Webapps:AddApp",{ id: id, app: appObject }); if(aData.isPackage && aData.autoInstall){ // Skip directly to onInstallSuccessAck, since there isn't // a WebappsRegistry to receive Webapps:Install:Return:OK and respond // Webapps:Install:Return:Ack when an app is being auto-installed. this.onInstallSuccessAck(app.manifestURL); }else{ // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify // the installing page about the successful install, after which it'll // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck. this.broadcastMessage("Webapps:Install:Return:OK", aData); }
if(!aData.isPackage){ this.updateAppHandlers(null, app.manifest, app); if(aInstallSuccessCallback){ aInstallSuccessCallback(app.manifest); } }
Services.obs.notifyObservers(null,"webapps-installed", JSON.stringify({ manifestURL: app.manifestURL }));
if(aData.forceSuccessAck){ // If it's a local install, there's no content process so just // ack the install. this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork); } }), |
其中Task.async返回一个function,在function中会异步的执行传入的callback。所以主要逻辑还是在上面的函数中。在onInstallSuccessAck中会通过AppDownloadManager.jsm去下载app的数据,并保存到对应目录中。(看函数注释说是去下载,但实际code没有???)
5.2.5. DOMApplicationsRegistry.getSelf
DOMApplicationsRegistry.install的实现体在Webapps.js (gecko\dom\apps\src)中实现如下:
getSelf: function(){ let request =this.createRequest(); this.addMessageListeners("Webapps:GetSelf:Return:OK"); cpmm.sendAsyncMessage("Webapps:GetSelf",{ origin:this._getOrigin(this._window.location.href), appId:this._window.document.nodePrincipal.appId, oid:this._id, requestID:this.getRequestId(request)}); return request; }, |
可以看出主要是通过CPMM向B2G发送了"Webapps: GetSelf "消息,其中传递给B2G的数据是包括origin,appId,oid等信息。
Webapps:GetSelf消息的处理是在Webapps.jsm文件的receiveMessage中:
case"Webapps:GetSelf": this.getSelf(msg, mm); break; |
getSelf: function(aData, aMm){ aData.apps =[];
if(aData.appId == Ci.nsIScriptSecurityManager.NO_APP_ID || aData.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID){ aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); return; }
let tmp =[];
for(let id in this.webapps){ if(this.webapps[id].origin == aData.origin && this.webapps[id].localId == aData.appId&& this._isLaunchable(this.webapps[id])){ let app = AppsUtils.cloneAppObject(this.webapps[id]); aData.apps.push(app); tmp.push({ id: id }); break; } }
if(!aData.apps.length){ aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); return; }
this._readManifests(tmp).then((aResult)=>{ for(let i =0; i < aResult.length; i++) aData.apps[i].manifest = aResult[i].manifest; aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); }); }, |
获取到所有的符合条件的app信息(origin,localId相等)后返回。
5.2.6. DOMApplicationsRegistry.getInstalled
DOMApplicationsRegistry.getInstalled的实现体在Webapps.js (gecko\dom\apps\src)中实现如下:
getInstalled: function(){ let request =this.createRequest(); this.addMessageListeners("Webapps:GetInstalled:Return:OK"); cpmm.sendAsyncMessage("Webapps:GetInstalled",{ origin:this._getOrigin(this._window.location.href), oid:this._id, requestID:this.getRequestId(request)}); return request; }, |
可以看出主要是通过CPMM向B2G发送了"Webapps:GetInstalled"消息,其中传递给B2G的数据是包括origin,appId,oid等信息。
Webapps:GetInstalled消息的处理是在Webapps.jsm文件的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); }); }, |
与getSelf的处理类似,只是判断条件不同。
6. mozAudioChannelManager
7. mozCamera
8. mozFMRadio
9. mozMobileConnection
10. mozMobileMessage
11. mozNetworkStats
12. mozNfc
13. mozNotification
13.1. 介绍
13.1.1. mozNotification
mozNotification提供了创建Notification对象的方法,Notification主要用来在界面上提示用户一些必要的信息。
notification createNotification(in DOMString title, in DOMString description, in DOMString iconURL Optional); |
创建并返回一个Notification对象,用来显示的一段消息,可选的URL表示提示的图标。
notification createNotification( in DOMString title, in DOMString description, in DOMString iconURL Optional ); |
Title:Notification中显示的内容;
Description:Notification的描述;
iconURL:Notification的显示图标的URL。
"permissions":{ "desktop-notification":{} } |
在APP中使用Notification需要申请desktop-notification的权限。
13.1.2. Notification
Notification主要用来在界面上提示用户一些必要的信息。
var notification =new Notification(title, options) |
Title:Notification中显示的标题;
Options:可选参数,用来配置Notification,可以添加如下属性:
|
Notification含有如下属性:
- permission:只读,一个代表了当前Notification权限的字串。可能的值有:
denied
,用户选择拒绝显示;granted,用户选择显示;default,用户没有做出选择。
- title:构造函数中传入的title值。
- dir:构造函数中传入的options的dir属性值,参见前面的解释。
- lang:构造函数中传入的options的lang属性值,参见前面的解释。
- body:构造函数中传入的options的body属性值,参见前面的解释。
- tag:构造函数中传入的options的tag属性值,参见前面的解释。
- icon:构造函数中传入的options的icon属性值,参见前面的解释。
Notification.onclick:click事件的处理函数。当用户每次点击Notification时产生该事件。
Notification.onshow:show事件的处理函数。当Notification显示的时候产生该事件。
Notification.onerror:error事件的处理函数。当Notification遇到错误时产生该事件。
Notification.onclose:close事件的处理函数。当用户关闭了Notification时产生该事件。
Notification.requestPermission():弹出提示,请求用户给予显示Notification的权限
Notification.close():这个方法允许通过编程来关闭一个Notification。
EventTarget.addEventListener():注册指定事件的处理程序。
EventTarget.removeEventListener():注销一个事件处理程序。
EventTarget.dispatchEvent():dispatch事件。
function notifyMe(){ // Let's check if the browser supports notifications if(!("Notification" in window)){ alert("This browser does not support desktop notification"); }
// Let's check if the user is okay to get some notification elseif(Notification.permission ==="granted"){ // If it's okay let's create a notification var notification =new Notification("Hi there!"); }
// Otherwise, we need to ask the user for permission elseif(Notification.permission !=='denied'){ Notification.requestPermission(function (permission){ // If the user is okay, let's create a notification if(permission ==="granted"){ var notification =new Notification("Hi there!"); } }); }
// At last, if the user already denied any notification, and you // want to be respectful there is no need to bother them any more. } |
13.2. 原理分析
13.2.1. 初始化
当gaia层通过window.navigator. mozNotification访问mozNotification时,会调用到Navigator.cpp (gecko\dom\base)中的Navigator::GetmozNotification。
DesktopNotificationCenter* Navigator::GetMozNotification(ErrorResult& aRv) { if(mNotification){ return mNotification; }
if(!mWindow ||!mWindow->GetDocShell()){ aRv.Throw(NS_ERROR_FAILURE); returnnullptr; }
mNotification =new DesktopNotificationCenter(mWindow); return mNotification; } |
可以看出mozNotification是单实例对象,第一次访问是创建C++对应的实现体。
DesktopNotification.h (gecko\dom\src\notification):
DesktopNotificationCenter(nsPIDOMWindow *aWindow) { MOZ_ASSERT(aWindow); mOwner = aWindow;
nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(aWindow); MOZ_ASSERT(sop);
mPrincipal = sop->GetPrincipal(); MOZ_ASSERT(mPrincipal);
SetIsDOMBinding(); } |
13.2.2. createNotification
already_AddRefed<DesktopNotification> DesktopNotificationCenter::CreateNotification(const nsAString& aTitle, const nsAString& aDescription, const nsAString& aIconURL) { MOZ_ASSERT(mOwner);
nsRefPtr<DesktopNotification>notification= newDesktopNotification(aTitle, aDescription, aIconURL, mOwner, mPrincipal); notification->Init(); return notification.forget(); } |
可以看出每次调用CreateNotification会新创建一个DesktopNotification实例对象,并调用其初始化函数。
DesktopNotification::DesktopNotification(const nsAString & title, const nsAString & description, const nsAString & iconURL, nsPIDOMWindow *aWindow, nsIPrincipal* principal) : nsDOMEventTargetHelper(aWindow) ,mTitle(title) ,mDescription(description) ,mIconURL(iconURL) , mPrincipal(principal) , mAllow(false) , mShowHasBeenCalled(false) { if(Preferences::GetBool("notification.disabled",false)){ return; }
// If we are in testing mode (running mochitests, for example) // and we are suppose to allow requests, then just post an allow event. if(Preferences::GetBool("notification.prompt.testing",false)&& Preferences::GetBool("notification.prompt.testing.allow",true)){ mAllow =true; } } |
void DesktopNotification::Init() { nsRefPtr<DesktopNotificationRequest> request =newDesktopNotificationRequest(this);
// if we are in the content process, then remote it to the parent. if(XRE_GetProcessType()== GeckoProcessType_Content){
// if for some reason mOwner is null, just silently // bail. The user will not see a notification, and that // is fine. if(!GetOwner()) return;
// because owner implements nsITabChild, we can assume that it is // the one and only TabChild for this docshell. TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell());
// Retain a reference so the object isn't deleted without IPDL's knowledge. // Corresponding release occurs in DeallocPContentPermissionRequest. nsRefPtr<DesktopNotificationRequest> copy = request;
nsTArray<PermissionRequest> permArray; nsTArray<nsString> emptyOptions; permArray.AppendElement(PermissionRequest( NS_LITERAL_CSTRING("desktop-notification"), NS_LITERAL_CSTRING("unused"), emptyOptions)); child->SendPContentPermissionRequestConstructor(copy.forget().take(), permArray, IPC::Principal(mPrincipal));
request->Sendprompt(); return; }
// otherwise, dispatch it NS_DispatchToMainThread(request); } |
classDesktopNotificationRequest:public nsIContentPermissionRequest, public nsRunnable, public PCOMContentPermissionRequestChild
{ public:
NS_IMETHOD Run() MOZ_OVERRIDE { nsCOMPtr<nsIContentPermissionPrompt> prompt = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID); if(prompt){ prompt->Prompt(this); } return NS_OK; } nsRefPtr<DesktopNotification>mDesktopNotification; }; |
NS_CONTENT_PERMISSION_PROMPT_CONTRACTID字串的定义为@mozilla.org/content-permission/prompt;1,对应的组件实现在ContentPermissionPrompt.js中。
component {8c719f03-afe0-4aac-91ff-6c215895d467} ContentPermissionPrompt.js contract @mozilla.org/content-permission/prompt;1{8c719f03-afe0-4aac-91ff-6c215895d467} |
推测这里是向用户请求权限的界面。(??)
13.2.3. show()
void DesktopNotification::Show(ErrorResult& aRv) { mShowHasBeenCalled =true;
if(!mAllow){ return; }
aRv =PostDesktopNotification(); } |
nsresult DesktopNotification::PostDesktopNotification() { if(!mObserver){ mObserver =new AlertServiceObserver(this); }
#ifdef MOZ_B2G nsCOMPtr<nsIAppNotificationService> appNotifier = do_GetService("@mozilla.org/system-alerts-service;1"); if(appNotifier){ nsCOMPtr<nsPIDOMWindow> window = GetOwner(); uint32_t appId =(window.get())->GetDoc()->NodePrincipal()->GetAppId();
if(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID){ nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1"); nsString manifestUrl = EmptyString(); appsService->GetManifestURLByLocalId(appId, manifestUrl); mozilla::AutoSafeJSContext cx; JS::Rooted<JS::Value> val(cx); AppNotificationServiceOptions ops; ops.mTextClickable =true; ops.mManifestURL = manifestUrl;
if(!ops.ToObject(cx, JS::NullPtr(),&val)){ return NS_ERROR_FAILURE; }
return appNotifier->ShowAppNotification(mIconURL, mTitle, mDescription, mObserver, val); } } #endif
nsCOMPtr<nsIAlertsService> alerts = do_GetService("@mozilla.org/alerts-service;1"); if(!alerts){ return NS_ERROR_NOT_IMPLEMENTED; }
// Generate a unique name (which will also be used as a cookie) because // the nsIAlertsService will coalesce notifications with the same name. // In the case of IPC, the parent process will use the cookie to map // to nsIObservers, thus cookies must be unique to differentiate observers. nsString uniqueName = NS_LITERAL_STRING("desktop-notification:"); uniqueName.AppendInt(sCount++); nsIPrincipal* principal = GetOwner()->GetDoc()->NodePrincipal(); returnalerts->ShowAlertNotification(mIconURL,mTitle,mDescription, true, uniqueName, mObserver, uniqueName, NS_LITERAL_STRING("auto"), EmptyString(), principal); } |
其中@mozilla.org/alerts-service;1对应的组件为在nsAlertsService.cpp (gecko\toolkit\components\alerts)文件中的nsAlertsService。
NS_IMETHODIMP nsAlertsService::ShowAlertNotification(const nsAString & aImageUrl,const nsAString & aAlertTitle, const nsAString & aAlertText,bool aAlertTextClickable, const nsAString & aAlertCookie, nsIObserver * aAlertListener, const nsAString & aAlertName, const nsAString & aBidi, const nsAString & aLang, nsIPrincipal * aPrincipal) { if(XRE_GetProcessType()== GeckoProcessType_Content){ ContentChild* cpc = ContentChild::GetSingleton();
if(aAlertListener) cpc->AddRemoteAlertObserver(PromiseFlatString(aAlertCookie), aAlertListener);
cpc->SendShowAlertNotification(PromiseFlatString(aImageUrl), PromiseFlatString(aAlertTitle), PromiseFlatString(aAlertText), aAlertTextClickable, PromiseFlatString(aAlertCookie), PromiseFlatString(aAlertName), PromiseFlatString(aBidi), PromiseFlatString(aLang), IPC::Principal(aPrincipal)); return NS_OK; }
#ifdef MOZ_WIDGET_ANDROID mozilla::AndroidBridge::Bridge()->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertCookie, aAlertListener, aAlertName); return NS_OK; #else // Check if there is an optional service that handles system-level notifications nsCOMPtr<nsIAlertsService> sysAlerts(do_GetService(NS_SYSTEMALERTSERVICE_CONTRACTID)); nsresult rv; if(sysAlerts){ return sysAlerts->ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, aAlertCookie, aAlertListener, aAlertName, aBidi, aLang, IPC::Principal(aPrincipal)); }
if(!ShouldShowAlert()){ // Do not display the alert. Instead call alertfinished and get out. if(aAlertListener) aAlertListener->Observe(nullptr,"alertfinished", PromiseFlatString(aAlertCookie).get()); return NS_OK; }
// Use XUL notifications as a fallback if above methods have failed. rv = mXULAlerts.ShowAlertNotification(aImageUrl, aAlertTitle, aAlertText, aAlertTextClickable, aAlertCookie, aAlertListener, aAlertName, aBidi, aLang); return rv; #endif // !MOZ_WIDGET_ANDROID } |
component {fe33c107-82a4-41d6-8c64-5353267e04c9} AlertsService.js contract @mozilla.org/system-alerts-service;1{fe33c107-82a4-41d6-8c64-5353267e04c9} |
分析AlertsService.js 中实现的ShowAlertNotification功能:
AlertsService.js (gecko\b2g\components) 4860 2014/12/1 |
// nsIAlertsService showAlertNotification: function showAlertNotification(aImageUrl, aTitle, aText, aTextClickable, aCookie, aAlertListener, aName, aBidi, aLang){ let browser = Services.wm.getMostRecentWindow("navigator:browser"); browser.AlertsHelper.showAlertNotification(aImageUrl, aTitle, aText, aTextClickable, aCookie, aAlertListener, aName, aBidi, aLang); }, |
14. mozPower
15. mozSettings
16. mozSms
17. mozSocial
18. mozTCPSocket
19. mozTelephony
19.1. 介绍
返回一个你可以用来从浏览器启动和控制电话呼叫的Telephony对象。
var phone = window.navigator.mozTelephony; |
Telephony提供了拨打电话,接听电话和管理电话的功能。
Telephony.active:一个TelephonyCall对象,表示了当前正激活的电话。当前激活的电话是指从microphone或者通过Telephony.startTone()方法产生的tone。
Telephony.calls,一个TelephonyCall类型的数组,每一个call当前都是处于连接状态。
Telephony.muted:设置为true,则microphone设置为mute状态,false则打开microphone。
Telephony.speakerEnabled:设置为true,则speakerphone设置为mute状态,false则打开speakerphone。
Telephony.oncallschanged:处理callschanged事件的处理器;当前calls列表发生改变是产生这个事件。
Telephony.onincoming:处理incoming事件的处理器;当有新的电话拨入的时候产生这个事件。
Telephony.dial():拨打传入的号码,号码为字串形式。
Telephony.startTone():开始产生DTMF tones.
Telephony.stopTone():停止产生当前正在播放的DTMF tones.
19.2. 原理分析
19.2.1. 初始化
当gaia层通过window.navigator.mozTelephony访问mozTelephony时,会调用到Navigator.cpp (gecko\dom\base)中的Navigator::GetMozTelephony。
Telephony* Navigator::GetMozTelephony(ErrorResult& aRv) { if(!mTelephony){ if(!mWindow){ aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; } mTelephony =Telephony::Create(mWindow, aRv); }
return mTelephony; } |
可以看出Telephony的C++实现是一个单实例对象,第一次访问的时候进行初始化。
// static already_AddRefed<Telephony> Telephony::Create(nsPIDOMWindow* aOwner, ErrorResult& aRv) { NS_ASSERTION(aOwner,"Null owner!");
nsCOMPtr<nsITelephonyProvider> ril = do_GetService(TELEPHONY_PROVIDER_CONTRACTID); if(!ril){ aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; }
nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aOwner); if(!sgo){ aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; }
nsCOMPtr<nsIScriptContext> scriptContext = sgo->GetContext(); if(!scriptContext){ aRv.Throw(NS_ERROR_UNEXPECTED); return nullptr; }
nsRefPtr<Telephony> telephony =new Telephony(aOwner);
telephony->mProvider = ril; telephony->mListener =new Listener(telephony); telephony->mCallsList =new CallsList(telephony); telephony->mGroup = TelephonyCallGroup::Create(telephony);
nsresult rv = ril->EnumerateCalls(telephony->mListener); if(NS_FAILED(rv)){ aRv.Throw(rv); return nullptr; }
return telephony.forget(); } |
做了初始化的工作,返回的mozTelephony是一个C++的类Telephony的实例,其中成员ril的实现在TelephonyProvider.js (gecko\dom\telephony\gonk)中,为mozTelephony功能的主要实现者;mCallsList存储了当前connected状态的call信息;mGroup将信息存储的可能是跟电话会议相关的信息(多方通话??)。
19.2.2. Telephony.dial
在TelephonyBinding.cpp (objdir-gecko\dom\bindings)中会将Telephony.dial的调用,转换为Telephony.cpp (gecko\dom\telephony)中Telephony::Dial的调用。
already_AddRefed<Promise> Telephony::Dial(const nsAString& aNumber,const Optional<uint32_t>& aServiceId) { uint32_t serviceId = ProvidedOrDefaultServiceId(aServiceId); nsRefPtr<Promise> promise =DialInternal(serviceId, aNumber,false); return promise.forget(); } |
already_AddRefed<Promise> Telephony::DialInternal(uint32_t aServiceId,const nsAString& aNumber, bool aIsEmergency) { nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner()); if(!global){ returnnullptr; }
nsRefPtr<Promise>promise =new Promise(global);
if(!IsValidNumber(aNumber)||!IsValidServiceId(aServiceId)){ promise->MaybeReject(NS_LITERAL_STRING("InvalidAccessError")); return promise.forget(); }
// We only support one outgoing call at a time. if(HasDialingCall()){ promise->MaybeReject(NS_LITERAL_STRING("InvalidStateError")); return promise.forget(); }
nsCOMPtr<nsITelephonyCallback> callback = newCallback(this, promise, aServiceId, aNumber); nsresult rv =mProvider->Dial(aServiceId, aNumber, aIsEmergency, callback); if(NS_FAILED(rv)){ promise->MaybeReject(NS_LITERAL_STRING("InvalidStateError")); return promise.forget(); }
return promise.forget(); } |
返回一个Promise对象,调用TelephonyProvider.js中的Dial,执行后通过callback将信息返回给Promise。
dial: function(aClientId, aNumber, aIsEmergency, aTelephonyCallback){ ……
let isEmergencyNumber =this._isEmergencyNumber(aClientId,aNumber); let msg = isEmergencyNumber ? "dialEmergencyNumber": "dialNonEmergencyNumber"; this.isDialing =true; this._getClient(aClientId).sendWorkerMessage(msg,{ number: aNumber, isEmergency: isEmergencyNumber, isDialEmergency: aIsEmergency },(function(response){ this.isDialing =false; if(!response.success){ aTelephonyCallback.notifyDialError(response.errorMsg); returnfalse; }
if(response.isCdma){ onCdmaDialSuccess.call(this); }else{ aTelephonyCallback.notifyDialSuccess(); } returnfalse; }).bind(this)); }, |
_getClient: function(aClientId){ returngRadioInterfaceLayer.getRadioInterface(aClientId); }, |
RadioInterfaceLayer.js (gecko\dom\system\gonk) 168786 2014/12/2 |
sendWorkerMessage: function(rilMessageType, message, callback){ if(callback){ this.workerMessenger.send(rilMessageType, message, function(response){ return callback.handleResponse(response); }); }else{ this.workerMessenger.send(rilMessageType, message); } } |
可以看出,dail操作是通过sendWorkerMessage向RadioInterfaceLayer发送dialEmergencyNumber等信息,如果dail成功则回调aTelephonyCallback.notifyDialSuccess。
下面继续分析sendWorkerMessage流程:
function WorkerMessenger(){ // Initial owning attributes. this.radioInterfaces =[]; this.tokenCallbackMap ={};
this.worker =new ChromeWorker("resource://gre/modules/ril_worker.js"); this.worker.onerror =this.onerror.bind(this); this.worker.onmessage =this.onmessage.bind(this); } |
/** * Send arbitrary message to worker. * * @param rilMessageType * A text message type. * @param message [optional] * An optional message object to send. * @param callback [optional] * An optional callback function which is called when worker replies * with an message containing a "rilMessageToken" attribute of the * same value we passed. This callback function accepts only one * parameter -- the reply from worker. It also returns a boolean * value true to keep current token-callback mapping and wait for * another worker reply, or false to remove the mapping. */ send: function(clientId, rilMessageType, message, callback){ message = message ||{};
message.rilMessageClientId = clientId; message.rilMessageToken =this.token; this.token++;
if(callback){ // Only create the map if callback is provided. For sending a request // and intentionally leaving the callback undefined, that reply will // be dropped in |this.onmessage| because of that orphan token. // // For sending a request that never replied at all, we're fine with this // because no callback shall be passed and we leave nothing to be cleaned // up later. this.tokenCallbackMap[message.rilMessageToken]= callback; }
message.rilMessageType = rilMessageType; this.worker.postMessage(message); }, |
Ril_worker.js (gecko\dom\system\gonk) 492981 2014/12/26 |
onmessage = function onmessage(event){ ContextPool.handleChromeMessage(event.data); }; |
/** * Handle incoming messages from the main UI thread. * * @param message * Object containing the message. Messages are supposed */ handleChromeMessage: function(message){ if(DEBUG){ this.context.debug("Received chrome message "+ JSON.stringify(message)); } let method =this[message.rilMessageType]; if(typeof method !="function"){ if(DEBUG){ this.context.debug("Don't know what to do with message "+ JSON.stringify(message)); } return; } method.call(this, message); }, |
所以发送的dialEmergencyNumber消息,最终就调用到了Ril_worker.js中的dialEmergencyNumber方法:
/** * Dial an emergency number. * * @param number * String containing the number to dial. * @param clirMode * Integer for showing/hidding the caller Id to the called party. * @param uusInfo * Integer doing something XXX TODO */ dialEmergencyNumber: function(options){ let onerror =(function onerror(options, errorMsg){ options.success =false; options.errorMsg = errorMsg; this.sendChromeMessage(options); }).bind(this, options);
options.request = RILQUIRKS_REQUEST_USE_DIAL_EMERGENCY_CALL ? REQUEST_DIAL_EMERGENCY_CALL : REQUEST_DIAL; if(this.radioState == GECKO_RADIOSTATE_OFF){ if(DEBUG){ this.context.debug("Automatically enable radio for an emergency call."); }
if(!this.cachedDialRequest){ this.cachedDialRequest ={}; } this.cachedDialRequest.onerror = onerror; this.cachedDialRequest.callback =this.sendDialRequest.bind(this, options); this.setRadioEnabled({enabled:true}); return; }
this.sendDialRequest(options); }, |
sendDialRequest: function(options){ if(this._isCdma && Object.keys(this.currentCalls).length ==1){ // Make a Cdma 3way call. options.featureStr = options.number; this.sendCdmaFlashCommand(options); }else{ this.sendRilRequestDial(options); } }, |
sendRilRequestDial: function(options){ // Always suceed. options.success =true; this.sendChromeMessage(options);
let Buf =this.context.Buf; Buf.newParcel(options.request, options); Buf.writeString(options.number); Buf.writeInt32(options.clirMode || 0); Buf.writeInt32(options.uusInfo || 0); // TODO Why do we need this extra 0? It was put it in to make this // match the format of the binary message. Buf.writeInt32(0); Buf.sendParcel(); }, |
首先sendChromeMessage,参数options.success = true;
sendChromeMessage: function(message){ message.rilMessageClientId =this.context.clientId; postMessage(message); }, |
function(response){ this.isDialing =false; if(!response.success){ aTelephonyCallback.notifyDialError(response.errorMsg); returnfalse; }
if(response.isCdma){ onCdmaDialSuccess.call(this); }else{ aTelephonyCallback.notifyDialSuccess(); } returnfalse; } |
Telephony.cpp (gecko\dom\telephony) 21219 2014/12/2 classTelephony::Callback:publicnsITelephonyCallback { nsRefPtr<Telephony>mTelephony; nsRefPtr<Promise>mPromise; uint32_tmServiceId; nsStringmNumber;
public: NS_IMETHODIMP NotifyDialSuccess() { nsRefPtr<TelephonyCall>call= mTelephony->CreateNewDialingCall(mServiceId,mNumber);
mPromise->MaybeResolve(call); returnNS_OK; } };
|
会回调到TelephonyProvider.js的dail中设置的callback,由于设置了success属性,所以走到上面红色的部分aTelephonyCallback.notifyDialSuccess(),根据number生成创建一个DialingCall,并调用promise的then方法。
在向上返回结果的同时,也会向底层RIL发送真正的网络数据。最终调用Buf.sendParcel将数据通过RIL->modem发送出去,这个流程应该所有的RIL操作都是相同的,请参考文档《rilproxy-rilworker.docx》(作者neil.zhou)。我们这里主要关注的是Buf中数据的内容。
其中newParcel的实现如下:
/** * Start a new outgoing parcel. * * @param type * Integer specifying the request type. * @param options [optional] * Object containing information about the request, e.g. the * original main thread message object that led to the RIL request. */ newParcel: function(type, options){ if(DEBUG)this.context.debug("New outgoing parcel of type "+ type);
// We're going to leave room for the parcel size at the beginning. this.outgoingIndex =this.PARCEL_SIZE_SIZE; this.writeInt32(type); this.writeInt32(this.mToken);
if(!options){ options ={}; } options.rilRequestType = type; options.rilRequestError = null; this.mTokenRequestMap.set(this.mToken, options); this.mToken++; returnthis.mToken; }, |
所以发出数据的为type(32bit) + Token(32bit) + number(字串) + clirMode(32bit) + uusInfo(32bit) + 结束符0(32bit)。