WEB API机制分析

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中获取levelchargingremainingTime信息,存储在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属性访问

jsBatteryManager 4个只读属性:chargingchargingTimedischargingTimelevel,其对应的C实现见BatteryManagerBinding.cpp (objdir-gecko\dom\bindings)

对应的实现函数为get_chargingget_chargingTimeget_dischargingTimeget_level;然后分别调用mozilla::dom::battery::BatteryManager中的对应函数,直接读取mLevelmChargingmRemainingTime的值;当为充电状态的时候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对象,可以处理successerror消息。this.result是一个匿名的数组对象,每一个对象含有下面的属性:

id:代表alarmid的一个数字。

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.jsmDOMRequestHelper.jsm,并且定义当前的component对应的CIDIID值,加载CPMMappsService,推测后续是需要进行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值设置好,从而在successerror消息中可以访问到返回的数据。

最后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的权限(这个权限是在appmanifest中申请的,在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);

   },

先通过返回的IDgetRequest(json.requestId),然后将返回的数组中的数据转换为alarms数组,最后通过fireSuccess或者fireError回调到onsucess或者onerror

综上所述:所有的alarms信息到保存在一个数据库中,只有B2G进程才有权限读取数据库;客户端需要查询自己应用的alarms的时候,通过发送IPC消息到B2G进行查询,B2G进程先检查权限,然后从数据库中查询发请求的app的所有alarms返回回来

对应的时序图如下:

另外两个接口addremove的处理逻辑类似,不同只是发送的消息和代入的参数不同,转换为数据库操作的时候分别对应addremove操作。

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是否被安装了,传入的urlappmanifest

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

  }

};

如果请求获取的appdomain与当前的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.resultapp对象的数组。

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,发现里面有多个对象定义,查找CIDfff440b3-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"})

}

 

可以看出appB2G注册了"Webapps:Install:Return:OK,对应的消息是在Webapps.jsm (gecko\dom\apps\src)中处理的,当B2G进程发出消息,会通知到B2GWebapps.jsm,然后通过IPC消息转发到appWebapps.jsWebappsRegistryreceiveMessage会进行处理。

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;

}

可以看出appB2G注册了"Webapps:Install:Return:OK""Webapps:Uninstall:Return:OK""Webapps:Uninstall:Broadcast:Return:OK",对应的消息是在Webapps.jsm (gecko\dom\apps\src)中处理的,当B2G进程发出这些消息,会通知到B2GWebapps.jsm,然后通过IPC消息转发到appWebapps.jsWebappsRegistryreceiveMessage会进行处理,同时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,然后分别读取appmanifest信息,将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;

  },

 

可以看出主要是通过CPMMB2G发送了"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;

  },

可以看出主要是通过CPMMB2G发送了"Webapps: GetSelf "消息,其中传递给B2G的数据是包括originappIdoid等信息。

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信息(originlocalId相等)后返回。

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;

  },

可以看出主要是通过CPMMB2G发送了"Webapps:GetInstalled"消息,其中传递给B2G的数据是包括originappIdoid等信息。

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

);

TitleNotification中显示的内容;

DescriptionNotification的描述;

iconURLNotification的显示图标的URL

"permissions":{

    "desktop-notification":{}

}

APP中使用Notification需要申请desktop-notification的权限。

13.1.2. Notification

Notification主要用来在界面上提示用户一些必要的信息。

var notification =new Notification(title, options)

TitleNotification中显示的标题;

Options:可选参数,用来配置Notification,可以添加如下属性:

  1. dirNotification的显示方向,可以设置为auto, ltr, or rtl
  2. lang:显示Notification使用的语言。
  3. body:在Notification中显示的附加的内容
  4. tagNotificationID,可以用这个ID来获取,替换或者删除Notification
  5. icon:一个标识了Notification需要显示信息的图标。

Notification含有如下属性:

  1. permission:只读,一个代表了当前Notification权限的字串。可能的值有:denied,用户选择拒绝显示;granted,用户选择显示;default,用户没有做出选择。
  2. title:构造函数中传入的title值。
  3. dir:构造函数中传入的optionsdir属性值,参见前面的解释。
  4. lang:构造函数中传入的optionslang属性值,参见前面的解释。
  5. body:构造函数中传入的optionsbody属性值,参见前面的解释。
  6. tag:构造函数中传入的optionstag属性值,参见前面的解释。
  7. icon:构造函数中传入的optionsicon属性值,参见前面的解释。

Notification.onclickclick事件的处理函数。当用户每次点击Notification时产生该事件。

Notification.onshowshow事件的处理函数。当Notification显示的时候产生该事件。

Notification.onerrorerror事件的处理函数。当Notification遇到错误时产生该事件。

Notification.oncloseclose事件的处理函数。当用户关闭了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

}

其中引用到的NS_SYSTEMALERTSERVICE_CONTRACTID的定义为@mozilla.org/system-alerts-service;1,其实现文件为AlertsService.js

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;

}

可以看出TelephonyC++实现是一个单实例对象,第一次访问的时候进行初始化。

// 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操作是通过sendWorkerMessageRadioInterfaceLayer发送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.jsdail中设置的callback,由于设置了success属性,所以走到上面红色的部分aTelephonyCallback.notifyDialSuccess(),根据number生成创建一个DialingCall,并调用promisethen方法

在向上返回结果的同时,也会向底层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)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值