merge b2g-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 07 Jul 2014 15:05:36 +0200
changeset 192595 085eea991bb9fdd51221b20e46f3f51156eda1d7
parent 192537 9b0206fad5f231aed05bd3017e2dfc81b029986c (current diff)
parent 192594 0e682a7c77055ec636577a904193391a326f415a (diff)
child 192608 e9f6f79716c9654bb2cea5637a935aef986b7639
child 192655 072795da76bc319556e1d6eaee8f9e6333958957
child 192678 b2fd5a214d2ae521b60a1acb4ff7c3f648da2d52
push id27090
push usercbook@mozilla.com
push dateMon, 07 Jul 2014 13:07:36 +0000
treeherdermozilla-central@085eea991bb9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone33.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge b2g-inbound to mozilla-central a=merge
dom/apps/src/Webapps.jsm
dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_discovery.js
dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_getters.js
dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_setters.js
dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_adapteradded.js
dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_enabled.js
dom/ipc/TabChild.cpp
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="276ce45e78b09c4a4ee643646f691d22804754c1">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bf9aaf39dd5a6491925a022db167c460f8207d34"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "333f877ae4029d7cb1a0893f89da484d8e3cc14f", 
+    "revision": "278dd1b102a39cf2c48f11fe3038eaf8f0779d7d", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="cc67f31dc638c0b7edba3cf7e3d87cadf0ed52bf">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="0d616942c300d9fb142483210f1dda9096c9a9fc">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="93daa354671a698634a3dc661c8c9dcb7d824c31"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="99f56d9db3cd37c684b01de6fed786421f47e2b7"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="230f11aff069d90d20fc2dc63b48e9ae3d4bdcd1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="96cdde4b5b5d8d3785b36c3c68cd746aff3005cc"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="7f9ec13a30f1b2cc8bdb1a199b7da54b9ab8860f"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -400,16 +400,18 @@
 @BINPATH@/components/nsSidebar.js
 
 ; WiFi, NetworkManager, NetworkStats
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 @BINPATH@/components/DOMWifiP2pManager.js
 @BINPATH@/components/DOMWifiP2pManager.manifest
+@BINPATH@/components/EthernetManager.js
+@BINPATH@/components/EthernetManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkService.js
 @BINPATH@/components/NetworkService.manifest
 @BINPATH@/components/NetworkStatsManager.js
 @BINPATH@/components/NetworkStatsManager.manifest
--- a/dom/apps/src/AppsServiceChild.jsm
+++ b/dom/apps/src/AppsServiceChild.jsm
@@ -3,76 +3,345 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
-// This module exposes a subset of the functionnalities of the parent DOM
-// Registry to content processes, to be be used from the AppsService component.
+// This module exposes a subset of the functionalities of the parent DOM
+// Registry to content processes, to be used from the AppsService component.
 
-this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
+this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"];
 
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 function debug(s) {
   //dump("-*- AppsServiceChild.jsm: " + s + "\n");
 }
 
+const APPS_IPC_MSG_NAMES = [
+  "Webapps:AddApp",
+  "Webapps:RemoveApp",
+  "Webapps:UpdateApp",
+  "Webapps:CheckForUpdate:Return:KO",
+  "Webapps:FireEvent",
+  "Webapps:UpdateState"
+];
+
+// A simple cache for the wrapped manifests.
+this.WrappedManifestCache = {
+  _cache: { },
+
+  // Gets an entry from the cache, and populates the cache if needed.
+  get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
+    if (!aManifest) {
+      return;
+    }
+
+    if (!(aManifestURL in this._cache)) {
+      this._cache[aManifestURL] = { };
+    }
+
+    let winObjs = this._cache[aManifestURL];
+    if (!(aInnerWindowID in winObjs)) {
+      winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
+    }
+
+    return winObjs[aInnerWindowID];
+  },
+
+  // Invalidates an entry in the cache.
+  evict: function mcache_evict(aManifestURL, aInnerWindowID) {
+    debug("Evicting manifest " + aManifestURL + " window ID " +
+          aInnerWindowID);
+    if (aManifestURL in this._cache) {
+      let winObjs = this._cache[aManifestURL];
+      if (aInnerWindowID in winObjs) {
+        delete winObjs[aInnerWindowID];
+      }
+
+      if (Object.keys(winObjs).length == 0) {
+        delete this._cache[aManifestURL];
+      }
+    }
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    // Clear the cache on memory pressure.
+    this._cache = { };
+    Cu.forceGC();
+  },
+
+  init: function() {
+    Services.obs.addObserver(this, "memory-pressure", false);
+  }
+};
+
+this.WrappedManifestCache.init();
+
+
+// DOMApplicationRegistry keeps a cache containing a list of apps in the device.
+// This information is updated with the data received from the main process and
+// it is queried by the DOM objects to set their state.
+// This module handle all the messages broadcasted from the parent process,
+// including DOM events, which are dispatched to the corresponding DOM objects.
+
 this.DOMApplicationRegistry = {
+  // DOMApps will hold a list of arrays of weak references to
+  // mozIDOMApplication objects indexed by manifest URL.
+  DOMApps: {},
+
+  ready: false,
+  webapps: null,
+
   init: function init() {
-    debug("init");
     this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
                   .getService(Ci.nsISyncMessageSender);
 
-    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
+    APPS_IPC_MSG_NAMES.forEach((function(aMsgName) {
       this.cpmm.addMessageListener(aMsgName, this);
     }).bind(this));
 
+    this.cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
+      messages: APPS_IPC_MSG_NAMES
+    });
+
     // We need to prime the cache with the list of apps.
-    // XXX shoud we do this async and block callers if it's not yet there?
-    this.webapps = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
-
+    let list = this.cpmm.sendSyncMessage("Webapps:GetList", { })[0];
+    this.webapps = list.webapps;
     // We need a fast mapping from localId -> app, so we add an index.
+    // We also add the manifest to the app object.
     this.localIdIndex = { };
     for (let id in this.webapps) {
       let app = this.webapps[id];
       this.localIdIndex[app.localId] = app;
+      app.manifest = list.manifests[id];
     }
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   observe: function(aSubject, aTopic, aData) {
-    // cpmm.addMessageListener causes the DOMApplicationRegistry object to live
-    // forever if we don't clean up properly.
+    // cpmm.addMessageListener causes the DOMApplicationRegistry object to
+    // live forever if we don't clean up properly.
     this.webapps = null;
-    ["Webapps:AddApp", "Webapps:RemoveApp"].forEach((function(aMsgName) {
+    this.DOMApps = null;
+
+    APPS_IPC_MSG_NAMES.forEach((aMsgName) => {
       this.cpmm.removeMessageListener(aMsgName, this);
-    }).bind(this));
+    });
+
+    this.cpmm.sendAsyncMessage("Webapps:UnregisterForMessages",
+                               APPS_IPC_MSG_NAMES)
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     debug("Received " + aMessage.name + " message.");
-    let msg = aMessage.json;
+    let msg = aMessage.data;
     switch (aMessage.name) {
       case "Webapps:AddApp":
         this.webapps[msg.id] = msg.app;
         this.localIdIndex[msg.app.localId] = msg.app;
+        if (msg.manifest) {
+          this.webapps[msg.id].manifest = msg.manifest;
+        }
         break;
       case "Webapps:RemoveApp":
+        delete this.DOMApps[this.webapps[msg.id].manifestURL];
         delete this.localIdIndex[this.webapps[msg.id].localId];
         delete this.webapps[msg.id];
         break;
+      case "Webapps:UpdateApp":
+        let app = this.webapps[msg.oldId];
+        if (!app) {
+          return;
+        }
+
+        if (msg.app) {
+          for (let prop in msg.app) {
+            app[prop] = msg.app[prop];
+          }
+        }
+
+        this.webapps[msg.newId] = app;
+        this.localIdIndex[app.localId] = app;
+        delete this.webapps[msg.oldId];
+
+        let apps = this.DOMApps[msg.app.manifestURL];
+        if (!apps) {
+          return;
+        }
+        for (let i = 0; i < apps.length; i++) {
+          let domApp = apps[i].get();
+          if (!domApp) {
+            apps.splice(i);
+            continue;
+          }
+          domApp._proxy = new Proxy(domApp, {
+            get: function(target, prop) {
+              if (!DOMApplicationRegistry.webapps[msg.newId]) {
+                return;
+              }
+              return DOMApplicationRegistry.webapps[msg.newId][prop];
+            },
+            set: function(target, prop, val) {
+              if (!DOMApplicationRegistry.webapps[msg.newId]) {
+                return;
+              }
+              DOMApplicationRegistry.webapps[msg.newId][prop] = val;
+              return;
+            },
+          });
+        }
+        break;
+      case "Webapps:FireEvent":
+        this._fireEvent(aMessage);
+        break;
+      case "Webapps:UpdateState":
+        this._updateState(msg);
+        break;
+      case "Webapps:CheckForUpdate:Return:KO":
+        let DOMApps = this.DOMApps[msg.manifestURL];
+        if (!DOMApps || !msg.requestID) {
+          return;
+        }
+        DOMApps.forEach((DOMApp) => {
+          let domApp = DOMApp.get();
+          if (domApp && msg.requestID) {
+            domApp._fireRequestResult(aMessage, true /* aIsError */);
+          }
+        });
+        break;
     }
   },
 
+  /**
+   * mozIDOMApplication management
+   */
+
+  // Every time a DOM app is created, we save a weak reference to it that will
+  // be used to dispatch events and fire request results.
+  addDOMApp: function(aApp, aManifestURL, aId) {
+    let weakRef = Cu.getWeakReference(aApp);
+
+    if (!this.DOMApps[aManifestURL]) {
+      this.DOMApps[aManifestURL] = [];
+    }
+
+    let apps = this.DOMApps[aManifestURL];
+
+    // Get rid of dead weak references.
+    for (let i = 0; i < apps.length; i++) {
+      if (!apps[i].get()) {
+        apps.splice(i);
+      }
+    }
+
+    apps.push(weakRef);
+
+    // Each DOM app contains a proxy object used to build their state. We
+    // return the handler for this proxy object with traps to get and set
+    // app properties kept in the DOMApplicationRegistry app cache.
+    return {
+      get: function(target, prop) {
+        if (!DOMApplicationRegistry.webapps[aId]) {
+          return;
+        }
+        return DOMApplicationRegistry.webapps[aId][prop];
+      },
+      set: function(target, prop, val) {
+        if (!DOMApplicationRegistry.webapps[aId]) {
+          return;
+        }
+        DOMApplicationRegistry.webapps[aId][prop] = val;
+        return;
+      },
+    };
+  },
+
+  _fireEvent: function(aMessage) {
+    let msg = aMessage.data;
+    debug("_fireEvent " + JSON.stringify(msg));
+    if (!this.DOMApps || !msg.manifestURL || !msg.eventType) {
+      return;
+    }
+
+    let DOMApps = this.DOMApps[msg.manifestURL];
+    if (!DOMApps) {
+      return;
+    }
+
+    // The parent might ask childs to trigger more than one event in one
+    // shot, so in order to avoid needless IPC we allow an array for the
+    // 'eventType' IPC message field.
+    if (!Array.isArray(msg.eventType)) {
+      msg.eventType = [msg.eventType];
+    }
+
+    DOMApps.forEach((DOMApp) => {
+      let domApp = DOMApp.get();
+      if (!domApp) {
+        return;
+      }
+      msg.eventType.forEach((aEventType) => {
+        if ('on' + aEventType in domApp) {
+          domApp._fireEvent(aEventType);
+        }
+      });
+
+      if (msg.requestID) {
+        aMessage.data.result = msg.manifestURL;
+        domApp._fireRequestResult(aMessage);
+      }
+    });
+  },
+
+  _updateState: function(aMessage) {
+    if (!this.DOMApps || !aMessage.id) {
+      return;
+    }
+
+    let app = this.webapps[aMessage.id];
+    if (!app) {
+      return;
+    }
+
+    if (aMessage.app) {
+      for (let prop in aMessage.app) {
+        app[prop] = aMessage.app[prop];
+      }
+    }
+
+    if ("error" in aMessage) {
+      app.downloadError = aMessage.error;
+    }
+
+    if (aMessage.manifest) {
+      app.manifest = aMessage.manifest;
+      // Evict the wrapped manifest cache for all the affected DOM objects.
+      let DOMApps = this.DOMApps[app.manifestURL];
+      if (!DOMApps) {
+        return;
+      }
+      DOMApps.forEach((DOMApp) => {
+        let domApp = DOMApp.get();
+        if (!domApp) {
+          return;
+        }
+        WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID);
+      });
+    }
+  },
+
+  /**
+   * nsIAppsService API
+   */
   getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
     debug("getAppByManifestURL " + aManifestURL);
     return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
   },
 
   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
     debug("getAppLocalIdByManifestURL " + aManifestURL);
     return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
@@ -84,17 +353,17 @@ this.DOMApplicationRegistry = {
   },
 
   getAppLocalIdByStoreId: function(aStoreId) {
     debug("getAppLocalIdByStoreId:" + aStoreId);
     return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
   },
 
   getAppByLocalId: function getAppByLocalId(aLocalId) {
-    debug("getAppByLocalId " + aLocalId);
+    debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready);
     let app = this.localIdIndex[aLocalId];
     if (!app) {
       debug("Ouch, No app!");
       return null;
     }
 
     return new mozIApplication(app);
   },
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -7,16 +7,17 @@ const Ci = Components.interfaces;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
+Cu.import("resource://gre/modules/AppsServiceChild.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
                                    "@mozilla.org/childprocessmessagemanager;1",
                                    "nsIMessageSender");
 
 function convertAppsArray(aApps, aWindow) {
   let apps = new aWindow.Array();
   for (let i = 0; i < aApps.length; i++) {
@@ -273,134 +274,121 @@ WebappsRegistry.prototype = {
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Registry"})
 }
 
 /**
   * mozIDOMApplication object
   */
 
-// A simple cache for the wrapped manifests.
-let manifestCache = {
-  _cache: { },
-
-  // Gets an entry from the cache, and populates the cache if needed.
-  get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
-    if (!(aManifestURL in this._cache)) {
-      this._cache[aManifestURL] = { };
-    }
-
-    let winObjs = this._cache[aManifestURL];
-    if (!(aInnerWindowID in winObjs)) {
-      winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
-    }
-
-    return winObjs[aInnerWindowID];
-  },
-
-  // Invalidates an entry in the cache.
-  evict: function mcache_evict(aManifestURL, aInnerWindowID) {
-    if (aManifestURL in this._cache) {
-      let winObjs = this._cache[aManifestURL];
-      if (aInnerWindowID in winObjs) {
-        delete winObjs[aInnerWindowID];
-      }
-
-      if (Object.keys(winObjs).length == 0) {
-        delete this._cache[aManifestURL];
-      }
-    }
-  },
-
-  observe: function(aSubject, aTopic, aData) {
-    // Clear the cache on memory pressure.
-    this._cache = { };
-  },
-
-  init: function() {
-    Services.obs.addObserver(this, "memory-pressure", false);
-  }
-};
-
 function createApplicationObject(aWindow, aApp) {
-  let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication);
+  let app = Cc["@mozilla.org/webapps/application;1"]
+              .createInstance(Ci.mozIDOMApplication);
   app.wrappedJSObject.init(aWindow, aApp);
   return app;
 }
 
 function WebappsApplication() {
   this.wrappedJSObject = this;
 }
 
 WebappsApplication.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   init: function(aWindow, aApp) {
+    let proxyHandler = DOMApplicationRegistry.addDOMApp(this,
+                                                        aApp.manifestURL,
+                                                        aApp.id);
+    this._proxy = new Proxy(this, proxyHandler);
+
     this._window = aWindow;
-    let principal = this._window.document.nodePrincipal;
-    this._appStatus = principal.appStatus;
-    this.origin = aApp.origin;
-    this._manifest = aApp.manifest;
-    this._updateManifest = aApp.updateManifest;
-    this.manifestURL = aApp.manifestURL;
-    this.receipts = aApp.receipts;
-    this.installOrigin = aApp.installOrigin;
-    this.installTime = aApp.installTime;
-    this.installState = aApp.installState || "installed";
-    this.removable = aApp.removable;
-    this.lastUpdateCheck = aApp.lastUpdateCheck ? aApp.lastUpdateCheck
-                                                : Date.now();
-    this.updateTime = aApp.updateTime ? aApp.updateTime
-                                      : aApp.installTime;
-    this.progress = NaN;
-    this.downloadAvailable = aApp.downloadAvailable;
-    this.downloading = aApp.downloading;
-    this.readyToApplyDownload = aApp.readyToApplyDownload;
-    this.downloadSize = aApp.downloadSize || 0;
 
     this._onprogress = null;
     this._ondownloadsuccess = null;
     this._ondownloaderror = null;
     this._ondownloadavailable = null;
     this._ondownloadapplied = null;
 
-    this._downloadError = null;
+    this.initDOMRequestHelper(aWindow);
+  },
+
+  get _appStatus() {
+    return this._proxy.appStatus;
+  },
+
+  get downloadAvailable() {
+    return this._proxy.downloadAvailable;
+  },
+
+  get downloading() {
+    return this._proxy.downloading;
+  },
 
-    this.initDOMRequestHelper(aWindow, [
-      { name: "Webapps:CheckForUpdate:Return:KO", weakRef: true },
-      { name: "Webapps:Connect:Return:OK", weakRef: true },
-      { name: "Webapps:Connect:Return:KO", weakRef: true },
-      { name: "Webapps:FireEvent", weakRef: true },
-      { name: "Webapps:GetConnections:Return:OK", weakRef: true },
-      { name: "Webapps:UpdateState", weakRef: true }
-    ]);
+  get downloadSize() {
+    return this._proxy.downloadSize;
+  },
+
+  get installOrigin() {
+    return this._proxy.installOrigin;
+  },
+
+  get installState() {
+    return this._proxy.installState;
+  },
+
+  get installTime() {
+    return this._proxy.installTime;
+  },
 
-    cpmm.sendAsyncMessage("Webapps:RegisterForMessages", {
-      messages: ["Webapps:FireEvent",
-                 "Webapps:UpdateState"],
-      app: {
-        id: this.id,
-        manifestURL: this.manifestURL,
-        installState: this.installState,
-        downloading: this.downloading
-      }
-    });
+  get lastUpdateCheck() {
+    return this._proxy.lastUpdateCheck;
+  },
+
+  get manifestURL() {
+    return this._proxy.manifestURL;
+  },
+
+  get origin() {
+    return this._proxy.origin;
+  },
+
+  get progress() {
+    return this._proxy.progress;
+  },
+
+  get readyToApplyDownload() {
+    return this._proxy.readyToApplyDownload;
+  },
+
+  get receipts() {
+    return this._proxy.receipts;
+  },
+
+  set receipts(aReceipts) {
+    this._proxy.receipts = aReceipts;
+  },
+
+  get removable() {
+    return this._proxy.removable;
+  },
+
+  get updateTime() {
+    return this._proxy.updateTime;
   },
 
   get manifest() {
-    return manifestCache.get(this.manifestURL,
-                             this._manifest,
-                             this._window,
-                             this.innerWindowID);
+    return WrappedManifestCache.get(this.manifestURL,
+                                    this._proxy.manifest,
+                                    this._window,
+                                    this.innerWindowID);
   },
 
   get updateManifest() {
-    return this.updateManifest =
-      this._updateManifest ? Cu.cloneInto(this._updateManifest, this._window)
-                           : null;
+    return this._proxy.updateManifest ?
+      Cu.cloneInto(this._proxy.updateManifest, this._window) : null;
   },
 
   set onprogress(aCallback) {
     this._onprogress = aCallback;
   },
 
   get onprogress() {
     return this._onprogress;
@@ -435,20 +423,20 @@ WebappsApplication.prototype = {
   },
 
   get ondownloadapplied() {
     return this._ondownloadapplied;
   },
 
   get downloadError() {
     // Only return DOMError when we have an error.
-    if (!this._downloadError) {
+    if (!this._proxy.downloadError) {
       return null;
     }
-    return new this._window.DOMError(this._downloadError);
+    return new this._window.DOMError(this._proxy.downloadError);
   },
 
   download: function() {
     cpmm.sendAsyncMessage("Webapps:Download",
                           { manifestURL: this.manifestURL });
   },
 
   cancelDownload: function() {
@@ -480,51 +468,55 @@ WebappsApplication.prototype = {
   },
 
   clearBrowserData: function() {
     let request = this.createRequest();
     let browserChild =
       BrowserElementPromptService.getBrowserElementChildForWindow(this._window);
     if (browserChild) {
       this.addMessageListeners("Webapps:ClearBrowserData:Return");
-      browserChild.messageManager.sendAsyncMessage(
-        "Webapps:ClearBrowserData",
-        { manifestURL: this.manifestURL,
-          oid: this._id,
-          requestID: this.getRequestId(request) }
-      );
+      browserChild.messageManager.sendAsyncMessage("Webapps:ClearBrowserData", {
+        manifestURL: this.manifestURL,
+        oid: this._id,
+        requestID: this.getRequestId(request)
+      });
     } else {
       Services.DOMRequest.fireErrorAsync(request, "NO_CLEARABLE_BROWSER");
     }
     return request;
   },
 
   connect: function(aKeyword, aRules) {
+    this.addMessageListeners(["Webapps:Connect:Return:OK",
+                              "Webapps:Connect:Return:KO"]);
     return this.createPromise(function (aResolve, aReject) {
-      cpmm.sendAsyncMessage("Webapps:Connect",
-                            { keyword: aKeyword,
-                              rules: aRules,
-                              manifestURL: this.manifestURL,
-                              outerWindowID: this._id,
-                              requestID: this.getPromiseResolverId({
-                                resolve: aResolve,
-                                reject: aReject
-                              })});
+      cpmm.sendAsyncMessage("Webapps:Connect", {
+        keyword: aKeyword,
+        rules: aRules,
+        manifestURL: this.manifestURL,
+        outerWindowID: this._id,
+        requestID: this.getPromiseResolverId({
+          resolve: aResolve,
+          reject: aReject
+        })
+      });
     }.bind(this));
   },
 
   getConnections: function() {
+    this.addMessageListeners("Webapps:GetConnections:Return:OK");
     return this.createPromise(function (aResolve, aReject) {
-      cpmm.sendAsyncMessage("Webapps:GetConnections",
-                            { manifestURL: this.manifestURL,
-                              outerWindowID: this._id,
-                              requestID: this.getPromiseResolverId({
-                                resolve: aResolve,
-                                reject: aReject
-                              })});
+      cpmm.sendAsyncMessage("Webapps:GetConnections", {
+        manifestURL: this.manifestURL,
+        outerWindowID: this._id,
+        requestID: this.getPromiseResolverId({
+          resolve: aResolve,
+          reject: aReject
+        })
+      });
     }.bind(this));
   },
 
   addReceipt: function(receipt) {
     let request = this.createRequest();
 
     this.addMessageListeners(["Webapps:AddReceipt:Return:OK",
                               "Webapps:AddReceipt:Return:KO"]);
@@ -563,141 +555,92 @@ WebappsApplication.prototype = {
                                                       oid: this._id,
                                                       requestID: this.getRequestId(request) });
 
     return request;
   },
 
   uninit: function() {
     this._onprogress = null;
-    cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [
-      "Webapps:FireEvent",
-      "Webapps:UpdateState"
-    ]);
-
-    manifestCache.evict(this.manifestURL, this.innerWindowID);
+    WrappedManifestCache.evict(this.manifestURL, this.innerWindowID);
   },
 
   _fireEvent: function(aName) {
     let handler = this["_on" + aName];
     if (handler) {
       let event = new this._window.MozApplicationEvent(aName, {
         application: this
       });
       try {
         handler.handleEvent(event);
       } catch (ex) {
         dump("Event handler expection " + ex + "\n");
       }
     }
   },
 
-  _updateState: function(aMsg) {
-    if (aMsg.app) {
-      for (let prop in aMsg.app) {
-        this[prop] = aMsg.app[prop];
-      }
+  _fireRequestResult: function(aMessage, aIsError) {
+    let req;
+    let msg = aMessage.data;
+    req = this.takeRequest(msg.requestID);
+    if (!req) {
+      return;
     }
 
-    // Intentional use of 'in' so we unset the error if this is explicitly null.
-    if ('error' in aMsg) {
-      this._downloadError = aMsg.error;
-    }
-
-    if (aMsg.manifest) {
-      this._manifest = aMsg.manifest;
-      manifestCache.evict(this.manifestURL, this.innerWindowID);
-    }
+    aIsError ? Services.DOMRequest.fireError(req, msg.error)
+             : Services.DOMRequest.fireSuccess(req, msg.result);
   },
 
   receiveMessage: function(aMessage) {
     let msg = aMessage.json;
     let req;
     if (aMessage.name == "Webapps:Connect:Return:OK" ||
         aMessage.name == "Webapps:Connect:Return:KO" ||
         aMessage.name == "Webapps:GetConnections:Return:OK") {
       req = this.takePromiseResolver(msg.requestID);
     } else {
       req = this.takeRequest(msg.requestID);
     }
 
-    // ondownload* callbacks should be triggered on all app instances
-    if ((msg.oid != this._id || !req) &&
-        aMessage.name !== "Webapps:FireEvent" &&
-        aMessage.name !== "Webapps:UpdateState") {
+    if (msg.oid !== this._id || !req) {
       return;
     }
 
     switch (aMessage.name) {
       case "Webapps:Launch:Return:KO":
         this.removeMessageListeners(["Webapps:Launch:Return:OK",
                                      "Webapps:Launch:Return:KO"]);
         Services.DOMRequest.fireError(req, "APP_INSTALL_PENDING");
         break;
       case "Webapps:Launch:Return:OK":
         this.removeMessageListeners(["Webapps:Launch:Return:OK",
                                      "Webapps:Launch:Return:KO"]);
         Services.DOMRequest.fireSuccess(req, null);
         break;
-      case "Webapps:CheckForUpdate:Return:KO":
-        Services.DOMRequest.fireError(req, msg.error);
-        break;
-      case "Webapps:FireEvent":
-        if (msg.manifestURL != this.manifestURL) {
-           return;
-        }
-
-        // The parent might ask childs to trigger more than one event in one
-        // shot, so in order to avoid needless IPC we allow an array for the
-        // 'eventType' IPC message field.
-        if (!Array.isArray(msg.eventType)) {
-          msg.eventType = [msg.eventType];
-        }
-
-        msg.eventType.forEach((aEventType) => {
-          // If we are in a successful state clear any past errors.
-          if (aEventType === 'downloadapplied' ||
-              aEventType === 'downloadsuccess') {
-            this._downloadError = null;
-          }
-
-          if ("_on" + aEventType in this) {
-            this._fireEvent(aEventType);
-          } else {
-            dump("Unsupported event type " + aEventType + "\n");
-          }
-        });
-
-        if (req) {
-          Services.DOMRequest.fireSuccess(req, this.manifestURL);
-        }
-        break;
-      case "Webapps:UpdateState":
-        if (msg.manifestURL != this.manifestURL) {
-          return;
-        }
-
-        this._updateState(msg);
-        break;
       case "Webapps:ClearBrowserData:Return":
         this.removeMessageListeners(aMessage.name);
         Services.DOMRequest.fireSuccess(req, null);
         break;
       case "Webapps:Connect:Return:OK":
+        this.removeMessageListeners(["Webapps:Connect:Return:OK",
+                                     "Webapps:Connect:Return:KO"]);
         let messagePorts = [];
         msg.messagePortIDs.forEach((aPortID) => {
           let port = new this._window.MozInterAppMessagePort(aPortID);
           messagePorts.push(port);
         });
         req.resolve(messagePorts);
         break;
       case "Webapps:Connect:Return:KO":
+        this.removeMessageListeners(["Webapps:Connect:Return:OK",
+                                     "Webapps:Connect:Return:KO"]);
         req.reject("No connections registered");
         break;
       case "Webapps:GetConnections:Return:OK":
+        this.removeMessageListeners(aMessage.name);
         let connections = [];
         msg.connections.forEach((aConnection) => {
           let connection =
             new this._window.MozInterAppConnection(aConnection.keyword,
                                                    aConnection.pubAppManifestURL,
                                                    aConnection.subAppManifestURL);
           connections.push(connection);
         });
@@ -869,22 +812,18 @@ WebappsApplicationMgmt.prototype = {
           let app = msg.app;
           let event = new this._window.MozApplicationEvent("applicationinstall",
                            { application : createApplicationObject(this._window, app) });
           this._oninstall.handleEvent(event);
         }
         break;
       case "Webapps:Uninstall:Broadcast:Return:OK":
         if (this._onuninstall) {
-          let detail = {
-            manifestURL: msg.manifestURL,
-            origin: msg.origin
-          };
           let event = new this._window.MozApplicationEvent("applicationuninstall",
-                           { application : createApplicationObject(this._window, detail) });
+                           { application : createApplicationObject(this._window, msg) });
           this._onuninstall.handleEvent(event);
         }
         break;
       case "Webapps:Uninstall:Return:OK":
         Services.DOMRequest.fireSuccess(req, msg.origin);
         break;
       case "Webapps:Uninstall:Return:KO":
         Services.DOMRequest.fireError(req, "NOT_INSTALLED");
@@ -903,12 +842,10 @@ WebappsApplicationMgmt.prototype = {
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
                                     contractID: "@mozilla.org/webapps/application-mgmt;1",
                                     interfaces: [Ci.mozIDOMApplicationMgmt],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Application Mgmt"})
 }
 
-manifestCache.init();
-
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebappsRegistry,
                                                      WebappsApplication]);
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1136,18 +1136,18 @@ this.DOMApplicationRegistry = {
         break;
       case "Webapps:UnregisterForMessages":
         this.removeMessageListener(msg, mm);
         break;
       case "child-process-shutdown":
         this.removeMessageListener(["Webapps:Internal:AllMessages"], mm);
         break;
       case "Webapps:GetList":
-        this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm);
-        return this.webapps;
+        return this.doGetList();
+        break;
       case "Webapps:Download":
         this.startDownload(msg.manifestURL);
         break;
       case "Webapps:CancelDownload":
         this.cancelDownload(msg.manifestURL);
         break;
       case "Webapps:CheckForUpdate":
         this.checkForUpdate(msg, mm);
@@ -1240,16 +1240,48 @@ this.DOMApplicationRegistry = {
       } else {
         deferred.resolve();
       }
     });
 
     return deferred.promise;
   },
 
+  /**
+    * Returns the full list of apps and manifests.
+    */
+  doGetList: function() {
+    let tmp = [];
+
+    for (let id in this.webapps) {
+      tmp.push({ id: id });
+    }
+
+    let res = {};
+    let done = false;
+
+    this._readManifests(tmp).then(
+      function(manifests) {
+        manifests.forEach((item) => {
+          res[item.id] = item.manifest;
+        });
+        done = true;
+      }
+    );
+
+    let thread = Services.tm.currentThread;
+    while (!done) {
+      //debug("before processNextEvent");
+      thread.processNextEvent(/* mayWait */ true);
+      //after("before processNextEvent");
+    }
+    return { webapps: this.webapps, manifests: res };
+  },
+
+
   doLaunch: function (aData, aMm) {
     this.launch(
       aData.manifestURL,
       aData.startPoint,
       aData.timestamp,
       function onsuccess() {
         aMm.sendAsyncMessage("Webapps:Launch:Return:OK", aData);
       },
@@ -1325,17 +1357,17 @@ this.DOMApplicationRegistry = {
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: {
           progress: 0,
           installState: download.previousState,
           downloading: false
         },
         error: error,
-        manifestURL: app.manifestURL,
+        id: app.id
       })
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
     });
     AppDownloadManager.remove(aManifestURL);
   },
@@ -1356,17 +1388,17 @@ this.DOMApplicationRegistry = {
       throw new Error("APP_IS_DOWNLOADING");
     }
 
     // If the caller is trying to start a download but we have nothing to
     // download, send an error.
     if (!app.downloadAvailable) {
       this.broadcastMessage("Webapps:UpdateState", {
         error: "NO_DOWNLOAD_AVAILABLE",
-        manifestURL: app.manifestURL
+        id: app.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
       throw new Error("NO_DOWNLOAD_AVAILABLE");
     }
 
@@ -1404,17 +1436,17 @@ this.DOMApplicationRegistry = {
         debug("No appcache found, sending 'downloaded' for " + aManifestURL);
         app.downloadAvailable = false;
 
         yield this._saveApps();
 
         this.broadcastMessage("Webapps:UpdateState", {
           app: app,
           manifest: jsonManifest,
-          manifestURL: aManifestURL
+          id: app.id
         });
         this.broadcastMessage("Webapps:FireEvent", {
           eventType: "downloadsuccess",
           manifestURL: aManifestURL
         });
       }
 
       return;
@@ -1448,17 +1480,17 @@ this.DOMApplicationRegistry = {
     app.downloadAvailable = false;
     app.readyToApplyDownload = true;
     app.updateTime = Date.now();
 
     yield this._saveApps();
 
     this.broadcastMessage("Webapps:UpdateState", {
       app: app,
-      manifestURL: aManifestURL
+      id: app.id
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "downloadsuccess",
       manifestURL: aManifestURL
     });
     if (app.installState == "pending") {
       // We restarted a failed download, apply it automatically.
       this.applyDownload(aManifestURL);
@@ -1550,17 +1582,17 @@ this.DOMApplicationRegistry = {
           manifestURL: app.manifestURL },
         true);
     }
     this.updateDataStore(this.webapps[id].localId, app.origin,
                          app.manifestURL, newManifest);
     this.broadcastMessage("Webapps:UpdateState", {
       app: app,
       manifest: newManifest,
-      manifestURL: app.manifestURL
+      id: app.id
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "downloadapplied",
       manifestURL: app.manifestURL
     });
   }),
 
   startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
@@ -1589,17 +1621,17 @@ this.DOMApplicationRegistry = {
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         // Clear any previous errors.
         error: null,
         app: {
           downloading: true,
           installState: aApp.installState,
           progress: 0
         },
-        manifestURL: aApp.manifestURL
+        id: aApp.id
       });
       let cacheUpdate = updateSvc.scheduleAppUpdate(
         appcacheURI, docURI, aApp.localId, false, aProfileDir);
 
       // We save the download details for potential further usage like
       // cancelling it.
       let download = {
         cacheUpdate: cacheUpdate,
@@ -1639,16 +1671,17 @@ this.DOMApplicationRegistry = {
       this.notifyAppsRegistryReady();
     }
   },
 
   checkForUpdate: function(aData, aMm) {
     debug("checkForUpdate for " + aData.manifestURL);
 
     function sendError(aError) {
+      debug("checkForUpdate error " + aError);
       aData.error = aError;
       aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
     }
 
     let id = this._appIdForManifestURL(aData.manifestURL);
     let app = this.webapps[id];
 
     // We cannot update an app that does not exists.
@@ -1668,71 +1701,67 @@ this.DOMApplicationRegistry = {
       sendError("APP_IS_DOWNLOADING");
       return;
     }
 
     // If the app is packaged and its manifestURL has an app:// scheme,
     // then we can't have an update.
     if (app.origin.startsWith("app://") &&
         app.manifestURL.startsWith("app://")) {
-      aData.error = "NOT_UPDATABLE";
-      aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
+      sendError("NOT_UPDATABLE");
       return;
     }
 
     // For non-removable hosted apps that lives in the core apps dir we
     // only check the appcache because we can't modify the manifest even
     // if it has changed.
     let onlyCheckAppCache = false;
 
 #ifdef MOZ_WIDGET_GONK
     let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
     onlyCheckAppCache = (app.basePath == appDir.path);
 #endif
 
     if (onlyCheckAppCache) {
       // Bail out for packaged apps.
       if (app.origin.startsWith("app://")) {
-        aData.error = "NOT_UPDATABLE";
-        aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
+        sendError("NOT_UPDATABLE");
         return;
       }
 
       // We need the manifest to check if we have an appcache.
       this._readManifests([{ id: id }]).then((aResult) => {
         let manifest = aResult[0].manifest;
         if (!manifest.appcache_path) {
-          aData.error = "NOT_UPDATABLE";
-          aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
+          sendError("NOT_UPDATABLE");
           return;
         }
 
         debug("Checking only appcache for " + aData.manifestURL);
         // Check if the appcache is updatable, and send "downloadavailable" or
         // "downloadapplied".
         let updateObserver = {
           observe: function(aSubject, aTopic, aObsData) {
             debug("onlyCheckAppCache updateSvc.checkForUpdate return for " +
                   app.manifestURL + " - event is " + aTopic);
             if (aTopic == "offline-cache-update-available") {
               app.downloadAvailable = true;
               this._saveApps().then(() => {
                 this.broadcastMessage("Webapps:UpdateState", {
                   app: app,
-                  manifestURL: app.manifestURL
+                  id: app.id
                 });
                 this.broadcastMessage("Webapps:FireEvent", {
                   eventType: "downloadavailable",
                   manifestURL: app.manifestURL,
                   requestID: aData.requestID
                 });
               });
             } else {
-              aData.error = "NOT_UPDATABLE";
-              aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
+              sendError("NOT_UPDATABLE");
             }
           }
         };
         let helper = new ManifestHelper(manifest, aData.manifestURL);
         debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
               helper.fullAppcachePath());
         updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
                                  app.localId, false, updateObserver);
@@ -1782,17 +1811,17 @@ this.DOMApplicationRegistry = {
             } else {
               this._saveApps().then(() => {
                 // Like if we got a 304, just send a 'downloadapplied'
                 // or downloadavailable event.
                 let eventType = app.downloadAvailable ? "downloadavailable"
                                                       : "downloadapplied";
                 aMm.sendAsyncMessage("Webapps:UpdateState", {
                   app: app,
-                  manifestURL: app.manifestURL
+                  id: app.id
                 });
                 aMm.sendAsyncMessage("Webapps:FireEvent", {
                   eventType: eventType,
                   manifestURL: app.manifestURL,
                   requestID: aData.requestID
                 });
               });
             }
@@ -1809,17 +1838,17 @@ this.DOMApplicationRegistry = {
           app.lastCheckedUpdate = Date.now();
           this._saveApps().then(() => {
             // If the app is a packaged app, we just send a 'downloadapplied'
             // or downloadavailable event.
             let eventType = app.downloadAvailable ? "downloadavailable"
                                                   : "downloadapplied";
             aMm.sendAsyncMessage("Webapps:UpdateState", {
               app: app,
-              manifestURL: app.manifestURL
+              id: app.id
             });
             aMm.sendAsyncMessage("Webapps:FireEvent", {
               eventType: eventType,
               manifestURL: app.manifestURL,
               requestID: aData.requestID
             });
           });
         } else {
@@ -1918,17 +1947,17 @@ this.DOMApplicationRegistry = {
     // event.
     aApp.downloadAvailable = true;
     aApp.downloadSize = manifest.size;
     aApp.updateManifest = aNewManifest;
     yield this._saveApps();
 
     this.broadcastMessage("Webapps:UpdateState", {
       app: aApp,
-      manifestURL: aApp.manifestURL
+      id: aApp.id
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "downloadavailable",
       manifestURL: aApp.manifestURL,
       requestID: aData.requestID
     });
   }),
 
@@ -1984,17 +2013,17 @@ this.DOMApplicationRegistry = {
     // Update the registry.
     this.webapps[aId] = aApp;
     yield this._saveApps();
 
     if (!manifest.appcache_path) {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aApp,
         manifest: aApp.manifest,
-        manifestURL: aApp.manifestURL
+        id: aApp.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloadapplied",
         manifestURL: aApp.manifestURL,
         requestID: aData.requestID
       });
     } else {
       // Check if the appcache is updatable, and send "downloadavailable" or
@@ -2018,17 +2047,17 @@ this.DOMApplicationRegistry = {
                                                   : "downloadapplied";
 
       aApp.downloadAvailable = (eventType == "downloadavailable");
       yield this._saveApps();
 
       this.broadcastMessage("Webapps:UpdateState", {
         app: aApp,
         manifest: aApp.manifest,
-        manifestURL: aApp.manifestURL
+        id: aApp.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: eventType,
         manifestURL: aApp.manifestURL,
         requestID: aData.requestID
       });
     }
 
@@ -2445,17 +2474,18 @@ this.DOMApplicationRegistry = {
 
     // Store the manifest and the updateManifest.
     this._writeManifestFile(app.id, false, aManifest);
     if (aUpdateManifest) {
       this._writeManifestFile(app.id, true, aUpdateManifest);
     }
 
     this._saveApps().then(() => {
-      this.broadcastMessage("Webapps:AddApp", { id: app.id, app: app });
+      this.broadcastMessage("Webapps:AddApp",
+                            { id: app.id, app: app, manifest: aManifest });
     });
   }),
 
   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);
@@ -2545,16 +2575,18 @@ this.DOMApplicationRegistry = {
       };
     }
 
     // We notify about the successful installation via mgmt.oninstall and the
     // corresponding DOMRequest.onsuccess event as soon as the app is properly
     // saved in the registry.
     yield this._saveApps();
 
+    aData.isPackage ? appObject.updateManifest = jsonManifest :
+                      appObject.manifest = jsonManifest;
     this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
 
     // The presence of a requestID means that we have a page to update.
     if (aData.isPackage && aData.apkInstall && !aData.requestID) {
       // 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);
@@ -2621,17 +2653,18 @@ this.DOMApplicationRegistry = {
     yield this._saveApps();
 
     this.updateAppHandlers(null, aManifest, aNewApp);
     // Clear the manifest cache in case it holds the update manifest.
     if (aId in this._manifestCache) {
       delete this._manifestCache[aId];
     }
 
-    this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp });
+    this.broadcastMessage("Webapps:AddApp",
+                          { id: aId, app: aNewApp, manifest: aManifest });
     Services.obs.notifyObservers(null, "webapps-installed",
       JSON.stringify({ manifestURL: aNewApp.manifestURL }));
 
     if (supportUseCurrentProfile()) {
       // Update the permissions for this app.
       PermissionsInstaller.installPermissions({
         manifest: aManifest,
         origin: aNewApp.origin,
@@ -2755,24 +2788,21 @@ this.DOMApplicationRegistry = {
 
       debug("About to download " + fullPackagePath);
 
       let requestChannel = this._getRequestChannel(fullPackagePath,
                                                    isLocalFileInstall,
                                                    oldApp,
                                                    aNewApp);
 
-      AppDownloadManager.add(
-        aNewApp.manifestURL,
-        {
-          channel: requestChannel,
-          appId: id,
-          previousState: aIsUpdate ? "installed" : "pending"
-        }
-      );
+      AppDownloadManager.add(aNewApp.manifestURL, {
+        channel: requestChannel,
+        appId: id,
+        previousState: aIsUpdate ? "installed" : "pending"
+      });
 
       // We set the 'downloading' flag to true right before starting the fetch.
       oldApp.downloading = true;
 
       // We determine the app's 'installState' according to its previous
       // state. Cancelled download should remain as 'pending'. Successfully
       // installed apps should morph to 'updating'.
       oldApp.installState = aIsUpdate ? "updating" : "pending";
@@ -2782,32 +2812,32 @@ this.DOMApplicationRegistry = {
 
       // Save the current state of the app to handle cases where we may be
       // retrying a past download.
       yield DOMApplicationRegistry._saveApps();
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         // Clear any previous download errors.
         error: null,
         app: oldApp,
-        manifestURL: aNewApp.manifestURL
+        id: id
       });
 
       let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp);
       let hash = yield this._computeFileHash(zipFile.path);
 
       let responseStatus = requestChannel.responseStatus;
       let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash);
 
       if (oldPackage) {
         debug("package's etag or hash unchanged; sending 'applied' event");
         // The package's Etag or hash has not changed.
         // We send an "applied" event right away so code awaiting that event
         // can proceed to access the app.  We also throw an error to alert
         // the caller that the package wasn't downloaded.
-        this._sendAppliedEvent(aNewApp, oldApp, id);
+        this._sendAppliedEvent(oldApp);
         throw new Error("PACKAGE_UNCHANGED");
       }
 
       let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp,
               isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash);
 
       AppDownloadManager.remove(aNewApp.manifestURL);
 
@@ -2937,17 +2967,17 @@ this.DOMApplicationRegistry = {
     return requestChannel;
   },
 
   _sendDownloadProgressEvent: function(aNewApp, aProgress) {
     this.broadcastMessage("Webapps:UpdateState", {
       app: {
         progress: aProgress
       },
-      manifestURL: aNewApp.manifestURL
+      id: aNewApp.id
     });
     this.broadcastMessage("Webapps:FireEvent", {
       eventType: "progress",
       manifestURL: aNewApp.manifestURL
     });
   },
 
   _getPackage: function(aRequestChannel, aId, aOldApp, aNewApp) {
@@ -3054,56 +3084,53 @@ this.DOMApplicationRegistry = {
   /**
    * Send an "applied" event right away for the package being installed.
    *
    * XXX We use this to exit the app update process early when the downloaded
    * package is identical to the last one we installed.  Presumably we do
    * something similar after updating the app, and we could refactor both cases
    * to use the same code to send the "applied" event.
    *
-   * @param aNewApp {Object} the new app data
-   * @param aOldApp {Object} the currently stored app data
-   * @param aId {String} the unique id of the app
+   * @param aApp {Object} app data
    */
-  _sendAppliedEvent: function(aNewApp, aOldApp, aId) {
-    aOldApp.downloading = false;
-    aOldApp.downloadAvailable = false;
-    aOldApp.downloadSize = 0;
-    aOldApp.installState = "installed";
-    aOldApp.readyToApplyDownload = false;
-    if (aOldApp.staged && aOldApp.staged.manifestHash) {
+  _sendAppliedEvent: function(aApp) {
+    aApp.downloading = false;
+    aApp.downloadAvailable = false;
+    aApp.downloadSize = 0;
+    aApp.installState = "installed";
+    aApp.readyToApplyDownload = false;
+    if (aApp.staged && aApp.staged.manifestHash) {
       // If we're here then the manifest has changed but the package
       // hasn't. Let's clear this, so we don't keep offering
       // a bogus update to the user
-      aOldApp.manifestHash = aOldApp.staged.manifestHash;
-      aOldApp.etag = aOldApp.staged.etag || aOldApp.etag;
-      aOldApp.staged = {};
-
-      // Move the staged update manifest to a non staged one.
+      aApp.manifestHash = aApp.staged.manifestHash;
+      aApp.etag = aApp.staged.etag || aApp.etag;
+      aApp.staged = {};
+     // Move the staged update manifest to a non staged one.
       try {
-        let staged = this._getAppDir(aId);
+        let staged = this._getAppDir(aApp.id);
         staged.append("staged-update.webapp");
         staged.moveTo(staged.parent, "update.webapp");
       } catch (ex) {
         // We don't really mind much if this fails.
       }
     }
 
     // Save the updated registry, and cleanup the tmp directory.
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
-        app: aOldApp,
-        manifestURL: aNewApp.manifestURL
+        app: aApp,
+        id: aApp.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
-        manifestURL: aNewApp.manifestURL,
+        manifestURL: aApp.manifestURL,
         eventType: ["downloadsuccess", "downloadapplied"]
       });
     });
-    let file = FileUtils.getFile("TmpD", ["webapps", aId], false);
+    let file = FileUtils.getFile("TmpD", ["webapps", aApp.id], false);
     if (file && file.exists()) {
       file.remove(true);
     }
   },
 
   _openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall,
                                 aIsUpdate, aManifest, aRequestChannel, aHash) {
     return Task.spawn((function*() {
@@ -3398,19 +3425,20 @@ this.DOMApplicationRegistry = {
         delete this.webapps[oldId];
         // Rename the directories where the files are installed.
         [DIRECTORY_NAME, "TmpD"].forEach(function(aDir) {
           let parent = FileUtils.getDir(aDir, ["webapps"], true, true);
           let dir = FileUtils.getDir(aDir, ["webapps", oldId], true, true);
           dir.moveTo(parent, newId);
         });
         // Signals that we need to swap the old id with the new app.
-        this.broadcastMessage("Webapps:RemoveApp", { id: oldId });
-        this.broadcastMessage("Webapps:AddApp", { id: newId,
-                                                  app: aOldApp });
+        this.broadcastMessage("Webapps:UpdateApp", { oldId: oldId,
+                                                     newId: newId,
+                                                     app: aOldApp });
+
       }
     }
   },
 
   _getIds: function(aIsSigned, aZipReader, aConverter, aNewApp, aOldApp,
                     aIsUpdate) {
     // Get ids.json if the file is signed
     if (aIsSigned) {
@@ -3503,17 +3531,17 @@ this.DOMApplicationRegistry = {
     if (aOldApp.staged) {
       delete aOldApp.staged;
     }
 
     this._saveApps().then(() => {
       this.broadcastMessage("Webapps:UpdateState", {
         app: aOldApp,
         error: aError,
-        manifestURL: aNewApp.manifestURL
+        id: aNewApp.id
       });
       this.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL:  aNewApp.manifestURL
       });
     });
     AppDownloadManager.remove(aNewApp.manifestURL);
 
@@ -4055,17 +4083,17 @@ let AppcacheObserver = function(aApp) {
 };
 
 AppcacheObserver.prototype = {
   // nsIOfflineCacheUpdateObserver implementation
   _sendProgressEvent: function() {
     let app = this.app;
     DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
       app: app,
-      manifestURL: app.manifestURL
+      id: app.id
     });
     DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
       eventType: "progress",
       manifestURL: app.manifestURL
     });
   },
 
   updateStateChanged: function appObs_Update(aUpdate, aState) {
@@ -4087,17 +4115,17 @@ AppcacheObserver.prototype = {
         return;
       }
 
       app.updateTime = Date.now();
       app.downloading = false;
       app.downloadAvailable = false;
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: app,
-        manifestURL: app.manifestURL
+        id: app.id
       });
       DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
         eventType: ["downloadsuccess", "downloadapplied"],
         manifestURL: app.manifestURL
       });
     }
 
     let setError = function appObs_setError(aError) {
@@ -4110,17 +4138,17 @@ AppcacheObserver.prototype = {
       if (app.isCanceling) {
         delete app.isCanceling;
         return;
       }
 
       DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
         app: app,
         error: aError,
-        manifestURL: app.manifestURL
+        id: app.id
       });
       DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
         eventType: "downloaderror",
         manifestURL: app.manifestURL
       });
     }
 
     switch (aState) {
--- a/dom/apps/tests/test_packaged_app_common.js
+++ b/dom/apps/tests/test_packaged_app_common.js
@@ -93,16 +93,17 @@ var PackagedTestHelper = (function Packa
       ok(false, "Got unexpected " + evt.target.error.name);
       finish();
     };
 
     navigator.mozApps.mgmt.oninstall = function(evt) {
       var aApp = evt.application;
       aApp.ondownloaderror = function(evt) {
         var error = aApp.downloadError.name;
+        ok(true, "Got downloaderror " + error);
         if (error == aExpectedError) {
           ok(true, "Got expected " + aExpectedError);
           var expected = {
             name: aName,
             manifestURL: aMiniManifestURL,
             installOrigin: gInstallOrigin,
             progress: 0,
             installState: "pending",
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -74,25 +74,25 @@ function checkLastAppState(aMiniManifest
 
 function updateApp(aExpectedReady, aPreviousVersion, aNextVersion) {
   var lApp = PackagedTestHelper.gApp;
 
   var ondownloadappliedhandler =
     checkLastAppState.bind(PackagedTestHelper, miniManifestURL, false, false,
                            aNextVersion, PackagedTestHelper.next);
 
-    var ondownloadsuccesshandler =
-      checkLastAppState.bind(undefined, miniManifestURL,
-                             aExpectedReady, false, aPreviousVersion,
-                             function() {
-        navigator.mozApps.mgmt.applyDownload(lApp);
-    });
+  var ondownloadsuccesshandler =
+    checkLastAppState.bind(undefined, miniManifestURL,
+                           aExpectedReady, false, aPreviousVersion,
+                           function() {
+      navigator.mozApps.mgmt.applyDownload(lApp);
+  });
 
-    checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler, null,
-                   true);
+  checkForUpdate(true, ondownloadsuccesshandler, ondownloadappliedhandler,
+                 null, true);
 
 }
 
 var initialPermissionState = {
   "geolocation": "prompt",
   "audio-capture": "prompt",
   "video-capture": "prompt",
   "downloads": "deny"
@@ -246,17 +246,17 @@ var steps = [
   },
   function() {
     info("== TEST == Update packaged app - Updating a pending app");
     miniManifestURL = PackagedTestHelper.gSJS +
                       "?getManifest=true" +
                       "&appName=arandomname" +
                       "&appToFail1";
     PackagedTestHelper.checkAppDownloadError(miniManifestURL,
-                                            "MANIFEST_MISMATCH", 2, false, true,
+                                            "MANIFEST_MISMATCH", 1, false, true,
                                              "arandomname",
                                              function () {
       checkForUpdate(false, null, null, null, false,
                      function (request) {
         if (request.error.name === "PENDING_APP_NOT_UPDATABLE") {
           info("Got expected PENDING_APP_NOT_UPDATEABLE");
         } else {
           ok(false, "Got unexpected " + request.error.name);
--- a/dom/apps/tests/test_receipt_operations.html
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -238,9 +238,9 @@ function runTest() {
   ok(true, "App uninstalled");
 }
 
 addLoadEvent(go);
 
 </script>
 </pre>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/dom/bluetooth2/tests/marionette/head.js
+++ b/dom/bluetooth2/tests/marionette/head.js
@@ -1,11 +1,9 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // https://github.com/mozilla-b2g/platform_external_qemu/blob/master/vl-android.c#L765
 // static int bt_hci_parse(const char *str) {
 //   ...
 //   bdaddr.b[0] = 0x52;
 //   bdaddr.b[1] = 0x54;
@@ -31,26 +29,78 @@ const EMULATOR_CLASS = 0x58020c;
 
 // Use same definition in QEMU for special bluetooth address,
 // which were defined at external/qemu/hw/bt.h:
 const BDADDR_ANY   = "00:00:00:00:00:00";
 const BDADDR_ALL   = "ff:ff:ff:ff:ff:ff";
 const BDADDR_LOCAL = "ff:ff:ff:00:00:00";
 
 // A user friendly name for remote BT device.
-const REMOTE_DEVICE_NAME = "Remote BT Device";
+const REMOTE_DEVICE_NAME = "Remote_BT_Device";
 
 let Promise =
   SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
 
 let bluetoothManager;
 
 let pendingEmulatorCmdCount = 0;
 
 /**
+ * Push required permissions and test if |navigator.mozBluetooth| exists.
+ * Resolve if it does, reject otherwise.
+ *
+ * Fulfill params:
+ *   bluetoothManager -- an reference to navigator.mozBluetooth.
+ * Reject params: (none)
+ *
+ * @param aPermissions
+ *        Additional permissions to push before any test cases.  Could be either
+ *        a string or an array of strings.
+ *
+ * @return A deferred promise.
+ */
+function ensureBluetoothManager(aPermissions) {
+  let deferred = Promise.defer();
+
+  let permissions = ["bluetooth"];
+  if (aPermissions) {
+    if (Array.isArray(aPermissions)) {
+      permissions = permissions.concat(aPermissions);
+    } else if (typeof aPermissions == "string") {
+      permissions.push(aPermissions);
+    }
+  }
+
+  let obj = [];
+  for (let perm of permissions) {
+    obj.push({
+      "type": perm,
+      "allow": 1,
+      "context": document,
+    });
+  }
+
+  SpecialPowers.pushPermissions(obj, function() {
+    ok(true, "permissions pushed: " + JSON.stringify(permissions));
+
+    bluetoothManager = window.navigator.mozBluetooth;
+    log("navigator.mozBluetooth is " +
+        (bluetoothManager ? "available" : "unavailable"));
+
+    if (bluetoothManager instanceof BluetoothManager) {
+      deferred.resolve(bluetoothManager);
+    } else {
+      deferred.reject();
+    }
+  });
+
+  return deferred.promise;
+}
+
+/**
  * Send emulator command with safe guard.
  *
  * We should only call |finish()| after all emulator command transactions
  * end, so here comes with the pending counter.  Resolve when the emulator
  * gives positive response, and reject otherwise.
  *
  * Fulfill params:
  *   result -- an array of emulator response lines.
@@ -189,81 +239,16 @@ function getEmulatorDeviceProperty(aAddr
   let cmd = "bt property " + aAddress + " " + aPropertyName;
   return runEmulatorCmdSafe(cmd)
     .then(function(aResults) {
       return aResults[0];
     });
 }
 
 /**
- * Start dicovering Bluetooth devices.
- *
- * Allows the device's adapter to start seeking for remote devices.
- *
- * Fulfill params: (none)
- * Reject params: a DOMError
- *
- * @param aAdapter
- *        A BluetoothAdapter which is used to interact with local BT dev
- *
- * @return A deferred promise.
- */
-function startDiscovery(aAdapter) {
-  let deferred = Promise.defer();
-
-  let request = aAdapter.startDiscovery();
-  request.onsuccess = function () {
-    log("  Start discovery - Success");
-    // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
-    //     Currently, discovering state wouldn't change immediately here.
-    //     We would turn on this check when the redesigned API are landed.
-    // is(aAdapter.discovering, true, "BluetoothAdapter.discovering");
-    deferred.resolve();
-  }
-  request.onerror = function (aEvent) {
-    ok(false, "Start discovery - Fail");
-    deferred.reject(aEvent.target.error);
-  }
-
-  return deferred.promise;
-}
-
-/**
- * Stop dicovering Bluetooth devices.
- *
- * Allows the device's adapter to stop seeking for remote devices.
- *
- * Fulfill params: (none)
- * Reject params: a DOMError
- *
- * @param aAdapter
- *        A BluetoothAdapter which is used to interact with local BT device.
- *
- * @return A deferred promise.
- */
-function stopDiscovery(aAdapter) {
-  let deferred = Promise.defer();
-
-  let request = aAdapter.stopDiscovery();
-  request.onsuccess = function () {
-    log("  Stop discovery - Success");
-    // TODO (bug 892207): Make Bluetooth APIs available for 3rd party apps.
-    //     Currently, discovering state wouldn't change immediately here.
-    //     We would turn on this check when the redesigned API are landed.
-    // is(aAdapter.discovering, false, "BluetoothAdapter.discovering");
-    deferred.resolve();
-  }
-  request.onerror = function (aEvent) {
-    ok(false, "Stop discovery - Fail");
-    deferred.reject(aEvent.target.error);
-  }
-  return deferred.promise;
-}
-
-/**
  * Get mozSettings value specified by @aKey.
  *
  * Resolve if that mozSettings value is retrieved successfully, reject
  * otherwise.
  *
  * Fulfill params:
  *   The corresponding mozSettings value of the key.
  * Reject params: (none)
@@ -314,29 +299,30 @@ function setSettings(aSettings) {
     ok(false, "setSettings(" + JSON.stringify(aSettings) + ")");
     deferred.reject();
   });
 
   return deferred.promise;
 }
 
 /**
- * Get mozSettings value of 'bluetooth.enabled'.
+ * Get the boolean value which indicates defaultAdapter of bluetooth is enabled.
  *
- * Resolve if that mozSettings value is retrieved successfully, reject
- * otherwise.
+ * Resolve if that defaultAdapter is enabled
  *
  * Fulfill params:
  *   A boolean value.
  * Reject params: (none)
  *
  * @return A deferred promise.
  */
 function getBluetoothEnabled() {
-  return getSettings("bluetooth.enabled");
+  log("bluetoothManager.defaultAdapter.state: " + bluetoothManager.defaultAdapter.state);
+
+  return (bluetoothManager.defaultAdapter.state == "enabled");
 }
 
 /**
  * Set mozSettings value of 'bluetooth.enabled'.
  *
  * Resolve if that mozSettings value is set successfully, reject otherwise.
  *
  * Fulfill params: (none)
@@ -349,68 +335,16 @@ function getBluetoothEnabled() {
  */
 function setBluetoothEnabled(aEnabled) {
   let obj = {};
   obj["bluetooth.enabled"] = aEnabled;
   return setSettings(obj);
 }
 
 /**
- * Push required permissions and test if |navigator.mozBluetooth| exists.
- * Resolve if it does, reject otherwise.
- *
- * Fulfill params:
- *   bluetoothManager -- an reference to navigator.mozBluetooth.
- * Reject params: (none)
- *
- * @param aPermissions
- *        Additional permissions to push before any test cases.  Could be either
- *        a string or an array of strings.
- *
- * @return A deferred promise.
- */
-function ensureBluetoothManager(aPermissions) {
-  let deferred = Promise.defer();
-
-  let permissions = ["bluetooth"];
-  if (aPermissions) {
-    if (Array.isArray(aPermissions)) {
-      permissions = permissions.concat(aPermissions);
-    } else if (typeof aPermissions == "string") {
-      permissions.push(aPermissions);
-    }
-  }
-
-  let obj = [];
-  for (let perm of permissions) {
-    obj.push({
-      "type": perm,
-      "allow": 1,
-      "context": document,
-    });
-  }
-
-  SpecialPowers.pushPermissions(obj, function() {
-    ok(true, "permissions pushed: " + JSON.stringify(permissions));
-
-    bluetoothManager = window.navigator.mozBluetooth;
-    log("navigator.mozBluetooth is " +
-        (bluetoothManager ? "available" : "unavailable"));
-
-    if (bluetoothManager instanceof BluetoothManager) {
-      deferred.resolve(bluetoothManager);
-    } else {
-      deferred.reject();
-    }
-  });
-
-  return deferred.promise;
-}
-
-/**
  * Wait for one named BluetoothManager event.
  *
  * Resolve if that named event occurs.  Never reject.
  *
  * Fulfill params: the DOMEvent passed.
  *
  * @param aEventName
  *        The name of the EventHandler.
@@ -453,83 +387,16 @@ function waitForAdapterEvent(aAdapter, a
     ok(true, "BluetoothAdapter event '" + aEventName + "' got.");
     deferred.resolve(aEvent);
   });
 
   return deferred.promise;
 }
 
 /**
- * Convenient function for setBluetoothEnabled and waitForManagerEvent
- * combined.
- *
- * Resolve if that named event occurs.  Reject if we can't set settings.
- *
- * Fulfill params: the DOMEvent passed.
- * Reject params: (none)
- *
- * @return A deferred promise.
- */
-function setBluetoothEnabledAndWait(aEnabled) {
-  let promises = [];
-
-  // Bug 969109 -  Intermittent test_dom_BluetoothManager_adapteradded.js
-  //
-  // Here we want to wait for two events coming up -- Bluetooth "settings-set"
-  // event and one of "enabled"/"disabled" events.  Special care is taken here
-  // to ensure that we can always receive that "enabled"/"disabled" event by
-  // installing the event handler *before* we ever enable/disable Bluetooth. Or
-  // we might just miss those events and get a timeout error.
-  promises.push(waitForManagerEvent(aEnabled ? "enabled" : "disabled"));
-  promises.push(setBluetoothEnabled(aEnabled));
-
-  return Promise.all(promises);
-}
-
-/**
- * Get default adapter.
- *
- * Resolve if that default adapter is got, reject otherwise.
- *
- * Fulfill params: a BluetoothAdapter instance.
- * Reject params: a DOMError, or null if if there is no adapter ready yet.
- *
- * @return A deferred promise.
- */
-function getDefaultAdapter() {
-  let deferred = Promise.defer();
-
-  let request = bluetoothManager.getDefaultAdapter();
-  request.onsuccess = function(aEvent) {
-    let adapter = aEvent.target.result;
-    if (!(adapter instanceof BluetoothAdapter)) {
-      ok(false, "no BluetoothAdapter ready yet.");
-      deferred.reject(null);
-      return;
-    }
-
-    ok(true, "BluetoothAdapter got.");
-    // TODO: We have an adapter instance now, but some of its attributes may
-    // still remain unassigned/out-dated.  Here we waste a few seconds to
-    // wait for the property changed events.
-    //
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=932914
-    window.setTimeout(function() {
-      deferred.resolve(adapter);
-    }, 3000);
-  };
-  request.onerror = function(aEvent) {
-    ok(false, "Failed to get default adapter.");
-    deferred.reject(aEvent.target.error);
-  };
-
-  return deferred.promise;
-}
-
-/**
  * Flush permission settings and call |finish()|.
  */
 function cleanUp() {
   waitFor(function() {
     SpecialPowers.flushPermissions(function() {
       // Use ok here so that we have at least one test run.
       ok(true, "permissions flushed");
 
@@ -547,42 +414,46 @@ function startBluetoothTestBase(aPermiss
       ok(false, "Unhandled rejected promise.");
       cleanUp();
     });
 }
 
 function startBluetoothTest(aReenable, aTestCaseMain) {
   startBluetoothTestBase(["settings-read", "settings-write"], function() {
     let origEnabled, needEnable;
+    return Promise.resolve()
+      .then(function() {
+        origEnabled = getBluetoothEnabled();
 
-    return getBluetoothEnabled()
-      .then(function(aEnabled) {
-        origEnabled = aEnabled;
-        needEnable = !aEnabled;
-        log("Original 'bluetooth.enabled' is " + origEnabled);
+        needEnable = !origEnabled;
+        log("Original state of bluetooth is " + bluetoothManager.defaultAdapter.state);
 
-        if (aEnabled && aReenable) {
-          log("  Disable 'bluetooth.enabled' ...");
+        if (origEnabled && aReenable) {
+          log("Disable Bluetooth ...");
           needEnable = true;
-          return setBluetoothEnabledAndWait(false);
+
+          isnot(bluetoothManager.defaultAdapter, null,
+            "bluetoothManager.defaultAdapter")
+
+          return bluetoothManager.defaultAdapter.disable();
         }
       })
       .then(function() {
         if (needEnable) {
-          log("  Enable 'bluetooth.enabled' ...");
+          log("Enable Bluetooth ...");
 
-          // See setBluetoothEnabledAndWait().  We must install all event
-          // handlers *before* enabling Bluetooth.
-          let promises = [];
-          promises.push(waitForManagerEvent("adapteradded"));
-          promises.push(setBluetoothEnabledAndWait(true));
-          return Promise.all(promises);
+          isnot(bluetoothManager.defaultAdapter, null,
+            "bluetoothManager.defaultAdapter")
+
+          return bluetoothManager.defaultAdapter.enable();
         }
       })
-      .then(getDefaultAdapter)
+      .then(() => bluetoothManager.defaultAdapter)
       .then(aTestCaseMain)
       .then(function() {
         if (!origEnabled) {
-          return setBluetoothEnabledAndWait(false);
+          log("Disable Bluetooth ...");
+
+          return bluetoothManager.defaultAdapter.disable();
         }
       });
   });
 }
--- a/dom/bluetooth2/tests/marionette/manifest.ini
+++ b/dom/bluetooth2/tests/marionette/manifest.ini
@@ -1,10 +1,6 @@
 [DEFAULT]
 b2g = true
 browser = false
-qemu = true
+qemu = false
 
-[test_dom_BluetoothManager_enabled.js]
-[test_dom_BluetoothManager_adapteradded.js]
-[test_dom_BluetoothAdapter_setters.js]
-[test_dom_BluetoothAdapter_getters.js]
-[test_dom_BluetoothAdapter_discovery.js]
+[test_dom_BluetoothManager_API2.js]
deleted file mode 100644
--- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_discovery.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-///////////////////////////////////////////////////////////////////////////////
-// Test Purpose:
-//   To verify that discovery process of BluetoothAdapter is correct.
-//   Use B2G emulator commands to add/remote remote devices to simulate
-//   discovering behavior.
-//
-// Test Coverage:
-//   - BluetoothAdapter.startDiscovery()
-//   - BluetoothAdapter.stopDiscovery()
-//   - BluetoothAdapter.ondevicefound()
-//   - BluetoothAdapter.discovering [Temporarily turned off until BT API update]
-//
-///////////////////////////////////////////////////////////////////////////////
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-startBluetoothTest(true, function testCaseMain(aAdapter) {
-  log("Testing the discovery process of BluetoothAdapter ...");
-
-  // The properties of remote device.
-  let theProperties = {
-    "name": REMOTE_DEVICE_NAME,
-    "discoverable": true
-  };
-
-  return Promise.resolve()
-    .then(() => removeEmulatorRemoteDevice(BDADDR_ALL))
-    .then(() => addEmulatorRemoteDevice(/*theProperties*/ null))
-    .then(function(aRemoteAddress) {
-      let promises = [];
-      promises.push(waitForAdapterEvent(aAdapter, "devicefound"));
-      promises.push(startDiscovery(aAdapter));
-      return Promise.all(promises)
-        .then(function(aResults) {
-          is(aResults[0].device.address, aRemoteAddress, "BluetoothDevice.address");
-        });
-    })
-    .then(() => stopDiscovery(aAdapter))
-    .then(() => removeEmulatorRemoteDevice(BDADDR_ALL));
-});
deleted file mode 100644
--- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_getters.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-///////////////////////////////////////////////////////////////////////////////
-// Test Purpose:
-//   To verify that the properties of BluetoothAdapter can be updated and
-//   retrieved correctly. Use B2G emulator commands to set properties for this
-//   test case.
-//
-// Test Coverage:
-//   - BluetoothAdapter.name
-//   - BluetoothAdapter.address
-//   - BluetoothAdapter.class
-//   - BluetoothAdapter.discoverable
-//   - BluetoothAdapter.discovering
-//   ( P.S. Don't include [BluetoothAdapter.uuids], [BluetoothAdapter.devices] )
-//
-///////////////////////////////////////////////////////////////////////////////
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-function testAdapterGetter(aAdapter, aPropertyName, aParamName, aExpected) {
-  let cmd = "bt property " + BDADDR_LOCAL + " " + aParamName;
-  return runEmulatorCmdSafe(cmd)
-    .then(function(aResults) {
-      is(aResults[1], "OK", "The status report from emulator command.");
-      log("  Got adapter " + aResults[0]);
-      is(aResults[0], aParamName + ": " + aExpected, "BluetoothAdapter." + aPropertyName);
-    });
-}
-
-startBluetoothTest(true, function testCaseMain(aAdapter) {
-  log("Checking the correctness of BluetoothAdapter properties ...");
-
-  return Promise.resolve()
-    .then(() => testAdapterGetter(aAdapter, "name",         "name",         aAdapter.name))
-    .then(() => testAdapterGetter(aAdapter, "address",      "address",      aAdapter.address))
-    .then(() => testAdapterGetter(aAdapter, "class",        "cod",          "0x" + aAdapter.class.toString(16)))
-    .then(() => testAdapterGetter(aAdapter, "discoverable", "discoverable", aAdapter.discoverable.toString()))
-    .then(() => testAdapterGetter(aAdapter, "discovering",  "discovering",  aAdapter.discovering.toString()));
-});
deleted file mode 100644
--- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothAdapter_setters.js
+++ /dev/null
@@ -1,85 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-///////////////////////////////////////////////////////////////////////////////
-// Test Purpose:
-//   To verify that all setters of BluetoothAdapter (except for pairing related
-//   APIs) can change properties correctly.
-//
-// Test Coverage:
-//   - BluetoothAdapter.setName()
-//   - BluetoothAdapter.setDiscoverable()
-//   - BluetoothAdapter.setDiscoverableTimeout()
-//
-///////////////////////////////////////////////////////////////////////////////
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-const BT_DEVICE_NAME = "User friendly name of local BT device";
-
-function setName(aAdapter, aName) {
-  let deferred = Promise.defer();
-
-  let request = aAdapter.setName(aName);
-  request.onsuccess = function () {
-    log("  setName succeed: " + aName);
-    is(aAdapter.name, aName, "aAdapter.name");
-    deferred.resolve();
-  }
-  request.onerror = function () {
-    ok(false, "setName failed")
-    deferred.reject();
-  }
-
-  return deferred.promise;
-}
-
-function setDiscoverable(aAdapter, aIsDiscoverable) {
-  let deferred = Promise.defer();
-
-  let request = aAdapter.setDiscoverable(aIsDiscoverable);
-  request.onsuccess = function () {
-    log("  setDiscoverable succeed: " + aIsDiscoverable);
-    is(aAdapter.discoverable, aIsDiscoverable, "aAdapter.discoverable");
-    deferred.resolve();
-  }
-  request.onerror = function () {
-    ok(false, "setDiscoverable failed")
-    deferred.reject();
-  }
-
-  return deferred.promise;
-}
-
-function setDiscoverableTimeout(aAdapter, aTimeout) {
-  let deferred = Promise.defer();
-
-  let request = aAdapter.setDiscoverableTimeout(aTimeout);
-  request.onsuccess = function () {
-    log("  setDiscoverableTimeout succeed: " + aTimeout);
-    is(aAdapter.discoverableTimeout, aTimeout, "aAdapter.discoverableTimeout");
-    deferred.resolve();
-  }
-  request.onerror = function () {
-    ok(false, "setDiscoverableTimeout failed")
-    deferred.reject();
-  }
-
-  return deferred.promise;
-}
-
-startBluetoothTest(true, function testCaseMain(aAdapter) {
-  log("Testing BluetoothAdapter setters ...");
-
-  return Promise.resolve()
-    .then( () => setName(aAdapter, BT_DEVICE_NAME) )
-    .then( () => setDiscoverableTimeout(aAdapter, 180) )
-    .then( () => setDiscoverable(aAdapter, true) )
-    .then( () => setName(aAdapter, EMULATOR_NAME) )
-    .then( () => setDiscoverable(aAdapter, false) )
-    .then( () => setDiscoverableTimeout(aAdapter, 120) );
-});
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_API2.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+///////////////////////////////////////////////////////////////////////////////
+// Test Purpose:
+//   To verify the basic functionality of BluetoothManager.
+//
+// Test Coverage:
+//   - BluetoothManager.defaultAdapter
+//   - BluetoothManager.getAdapters()
+// TODO:
+//   - BluetoothManager.onattributechanged()
+//   - BluetoothManager.onadapteradded()
+//   - BluetoothManager.onadapterremoved()
+//
+///////////////////////////////////////////////////////////////////////////////
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+// TODO: Listens to 'onattributechanged' when B2G supports the feature that
+//       allows user to add/remove Bluetooth adapter.
+// Currently, B2G recognizes build-in Bluetooth chip as default adapter and
+// don't support adding additional Bluetooth dongles in gonk layer.
+// Therefore, the 'onattributechanged' would be triggered *only* when the
+// instance of BluetoothManager is created.
+function waitForManagerAttributeChanged() {
+  let deferred = Promise.defer();
+
+  bluetoothManager.onattributechanged = function(aEvent) {
+    if(aEvent.attrs.indexOf("defaultAdapter")) {
+      bluetoothManager.onattributechanged = null;
+      ok(true, "BluetoothManager event 'onattributechanged' got.");
+      deferred.resolve(aEvent);
+    }
+  };
+
+  return deferred.promise;
+}
+
+startBluetoothTestBase(["settings-read", "settings-write"],
+                       function testCaseMain() {
+  let adapters = bluetoothManager.getAdapters();
+  ok(Array.isArray(adapters), "Can not got the array of adapters");
+  ok(adapters.length, "The number of adapters should not be zero");
+  ok(bluetoothManager.defaultAdapter, "defaultAdapter should not be null.");
+});
deleted file mode 100644
--- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_adapteradded.js
+++ /dev/null
@@ -1,19 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-startBluetoothTest(true, function testCaseMain(aAdapter) {
-  log("Checking adapter attributes ...");
-
-  is(aAdapter.name, EMULATOR_NAME, "adapter.name");
-  is(aAdapter.class, EMULATOR_CLASS, "adapter.class");
-  is(aAdapter.address, EMULATOR_ADDRESS, "adapter.address");
-  is(aAdapter.discovering, false, "adapter.discovering");
-  is(aAdapter.discoverable, false, "adapter.discoverable");
-  is(aAdapter.discoverableTimeout, 120, "adapter.discoverableTimeout");
-});
deleted file mode 100644
--- a/dom/bluetooth2/tests/marionette/test_dom_BluetoothManager_enabled.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim: sw=2 ts=2 sts=2 et filetype=javascript
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
-
-function waitEitherEnabledOrDisabled() {
-  let deferred = Promise.defer();
-
-  function onEnabledDisabled(aEvent) {
-    bluetoothManager.removeEventListener("adapteradded", onEnabledDisabled);
-    bluetoothManager.removeEventListener("disabled", onEnabledDisabled);
-
-    ok(true, "Got event " + aEvent.type);
-    deferred.resolve(aEvent.type === "adapteradded");
-  }
-
-  // Listen 'adapteradded' rather than 'enabled' since the current API can't
-  // disable BT before the BT adapter is initialized.
-  // We should listen to 'enabled' when gecko can handle the case I mentioned
-  // above, please refer to the follow-up bug 973482.
-  bluetoothManager.addEventListener("adapteradded", onEnabledDisabled);
-  bluetoothManager.addEventListener("disabled", onEnabledDisabled);
-
-  return deferred.promise;
-}
-
-function test(aEnabled) {
-  log("Testing 'bluetooth.enabled' => " + aEnabled);
-
-  let deferred = Promise.defer();
-
-  Promise.all([setBluetoothEnabled(aEnabled),
-               waitEitherEnabledOrDisabled()])
-    .then(function(aResults) {
-      /* aResults is an array of two elements:
-       *   [ <result of setBluetoothEnabled>,
-       *     <result of waitEitherEnabledOrDisabled> ]
-       */
-      log("  Examine results " + JSON.stringify(aResults));
-
-      is(bluetoothManager.enabled, aEnabled, "bluetoothManager.enabled");
-      is(aResults[1], aEnabled, "'adapteradded' event received");
-
-      if (bluetoothManager.enabled === aEnabled && aResults[1] === aEnabled) {
-        deferred.resolve();
-      } else {
-        deferred.reject();
-      }
-    });
-
-  return deferred.promise;
-}
-
-startBluetoothTestBase(["settings-read", "settings-write"],
-                       function testCaseMain() {
-  return getBluetoothEnabled()
-    .then(function(aEnabled) {
-      log("Original 'bluetooth.enabled' is " + aEnabled);
-      // Set to !aEnabled and reset back to aEnabled.
-      return test(!aEnabled).then(test.bind(null, aEnabled));
-    });
-});
--- a/dom/cellbroadcast/src/CellBroadcast.cpp
+++ b/dom/cellbroadcast/src/CellBroadcast.cpp
@@ -12,17 +12,17 @@
 #define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
 
 using namespace mozilla::dom;
 
 /**
  * CellBroadcast::Listener Implementation.
  */
 
-class CellBroadcast::Listener : public nsICellBroadcastListener
+class CellBroadcast::Listener MOZ_FINAL : public nsICellBroadcastListener
 {
 private:
   CellBroadcast* mCellBroadcast;
 
 public:
   NS_DECL_ISUPPORTS
   NS_FORWARD_SAFE_NSICELLBROADCASTLISTENER(mCellBroadcast)
 
--- a/dom/icc/src/IccListener.h
+++ b/dom/icc/src/IccListener.h
@@ -9,17 +9,17 @@
 #include "nsIIccProvider.h"
 
 namespace mozilla {
 namespace dom {
 
 class IccManager;
 class Icc;
 
-class IccListener : public nsIIccListener
+class IccListener MOZ_FINAL : public nsIIccListener
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIICCLISTENER
 
   IccListener(IccManager* aIccManager, uint32_t aClientId);
   ~IccListener();
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -759,21 +759,16 @@ TabChild::Observe(nsISupports *aSubject,
       nsCOMPtr<nsIDocument> doc(GetDocument());
 
       if (SameCOMIdentity(subject, doc)) {
         nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
         utils->SetIsFirstPaint(true);
 
         mContentDocumentIsDisplayed = true;
 
-        // Reset CSS viewport and zoom to default on new page, then
-        // calculate them properly using the actual metadata from the
-        // page.
-        SetCSSViewport(kDefaultViewportSize);
-
         // In some cases before-first-paint gets called before
         // RecvUpdateDimensions is called and therefore before we have an
         // mInnerSize value set. In such cases defer initializing the viewport
         // until we we get an inner size.
         if (HasValidInnerSize()) {
           InitializeRootMetrics();
           utils->SetResolution(mLastRootMetrics.mResolution.scale,
                                mLastRootMetrics.mResolution.scale);
--- a/dom/network/interfaces/moz.build
+++ b/dom/network/interfaces/moz.build
@@ -13,12 +13,13 @@ XPIDL_SOURCES += [
     'nsITCPSocketChild.idl',
     'nsITCPSocketParent.idl',
     'nsIUDPSocketChild.idl',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     XPIDL_SOURCES += [
         'nsIDOMNetworkStatsManager.idl',
+        'nsIEthernetManager.idl',
         'nsINetworkStatsServiceProxy.idl',
     ]
 
 XPIDL_MODULE = 'dom_network'
new file mode 100644
--- /dev/null
+++ b/dom/network/interfaces/nsIEthernetManager.idl
@@ -0,0 +1,137 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, function, uuid(2a3ad56c-edc0-439f-8aae-900b331ddf49)]
+interface nsIEthernetManagerCallback : nsISupports
+{
+  /**
+   * Callback function used to report the success of different operations.
+   *
+   * @param success
+   *        Boolean value indicates the success of an operation.
+   * @prarm message
+   *        Message reported in the end of operation.
+   */
+  void notify(in boolean success, in DOMString message);
+};
+
+[scriptable, function, uuid(1746e7dd-92d4-43fa-8ef4-bc13d0b60353)]
+interface nsIEthernetManagerScanCallback : nsISupports
+{
+  /**
+   * Callback function used to report the result of scan function.
+   *
+   * @param list
+   *        List of available ethernet interfaces.
+   */
+  void notify(in jsval list);
+};
+
+/**
+ * An internal idl provides control to ethernet interfaces.
+ */
+[scriptable, uuid(a96441dd-36b3-4f7f-963b-2c032e28a039)]
+interface nsIEthernetManager : nsISupports
+{
+  /**
+   * List of exisiting interface name.
+   */
+  readonly attribute jsval interfaceList;
+
+  /**
+   * Scan available ethernet interfaces on device.
+   *
+   * @param callback
+   *        Callback function.
+   */
+  void scan(in nsIEthernetManagerScanCallback callback);
+
+  /**
+   * Add a new interface to the interface list.
+   *
+   * @param ifname
+   *        Interface name. Should be the form of "eth*".
+   * @param callback
+   *        Callback function.
+   */
+  void addInterface(in DOMString ifname,
+                    in nsIEthernetManagerCallback callback);
+
+  /**
+   * Remove an existing interface from the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param Callback
+   *        Callback function.
+   */
+  void removeInterface(in DOMString ifname,
+                       in nsIEthernetManagerCallback callback);
+
+  /**
+   * Update a conifg of an existing interface in the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param config
+   *        .ip: ip address.
+   *        .prefixLength: mask length.
+   *        .gateway: gateway.
+   *        .dnses: dnses.
+   *        .httpProxyHost: http proxy host.
+   *        .httpProxyPort: http porxy port.
+   *        .ipMode: ip mode, can be 'dhcp' or 'static'.
+   * @param callback
+   *        Callback function.
+   */
+  void updateInterfaceConfig(in DOMString ifname,
+                             in jsval config,
+                             in nsIEthernetManagerCallback callback);
+
+  /**
+   * Enable networking of an existing interface in the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param callback
+   *        Callback function.
+   */
+  void enable(in DOMString ifname,
+              in nsIEthernetManagerCallback callback);
+
+  /**
+   * Disable networking of an existing interface in the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param callback
+   *        Callback function.
+   */
+  void disable(in DOMString ifname,
+               in nsIEthernetManagerCallback callback);
+
+  /**
+   * Make an existing interface connect to network.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param callback
+   *        Callback function.
+   */
+  void connect(in DOMString ifname,
+               in nsIEthernetManagerCallback callback);
+
+  /**
+   * Disconnect a connected interface in the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param callback
+   *        Callback function.
+   */
+  void disconnect(in DOMString ifname,
+                  in nsIEthernetManagerCallback callback);
+};
new file mode 100644
--- /dev/null
+++ b/dom/network/src/EthernetManager.js
@@ -0,0 +1,619 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DEBUG = false;
+function debug(s) {
+  if (DEBUG) {
+    dump("-*- EthernetManager: " + s + "\n");
+  }
+}
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const TOPIC_INTERFACE_STATE_CHANGED = "network-interface-state-changed";
+
+const ETHERNET_NETWORK_IFACE_PREFIX = "eth";
+const DEFAULT_ETHERNET_NETWORK_IFACE = "eth0";
+
+const INTERFACE_IPADDR_NULL = "0.0.0.0";
+const INTERFACE_GATEWAY_NULL = "0.0.0.0";
+const INTERFACE_PREFIX_NULL = 0;
+const INTERFACE_MACADDR_NULL = "00:00:00:00:00:00";
+
+const NETWORK_INTERFACE_UP   = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
+const IP_MODE_DHCP = "dhcp";
+const IP_MODE_STATIC = "static";
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+                                   "@mozilla.org/network/manager;1",
+                                   "nsINetworkManager");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+                                   "@mozilla.org/network/service;1",
+                                   "nsINetworkService");
+
+
+// nsINetworkInterface
+
+function EthernetInterface(attr) {
+  this.state = attr.state;
+  this.type = attr.type;
+  this.name = attr.name;
+  this.ipMode = attr.ipMode;
+  this.ips = [attr.ip];
+  this.prefixLengths = [attr.prefixLength];
+  this.gateways = [attr.gateway];
+  this.dnses = attr.dnses;
+  this.httpProxyHost = "";
+  this.httpProxyPort = 0;
+}
+EthernetInterface.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+  updateConfig: function(config) {
+    debug("Interface " + this.name + " updateConfig " + JSON.stringify(config));
+    this.state = (config.state != undefined) ?
+                  config.state : this.state;
+    this.ips = (config.ip != undefined) ? [config.ip] : this.ips;
+    this.prefixLengths = (config.prefixLength != undefined) ?
+                         [config.prefixLength] : this.prefixLengths;
+    this.gateways = (config.gateway != undefined) ?
+                    [config.gateway] : this.gateways;
+    this.dnses = (config.dnses != undefined) ? config.dnses : this.dnses;
+    this.httpProxyHost = (config.httpProxyHost != undefined) ?
+                          config.httpProxyHost : this.httpProxyHost;
+    this.httpProxyPort = (config.httpProxyPort != undefined) ?
+                          config.httpProxyPort : this.httpProxyPort;
+    this.ipMode = (config.ipMode != undefined) ?
+                   config.ipMode : this.ipMode;
+  },
+
+  getAddresses: function(ips, prefixLengths) {
+    ips.value = this.ips.slice();
+    prefixLengths.value = this.prefixLengths.slice();
+
+    return this.ips.length;
+  },
+
+  getGateways: function(count) {
+    if (count) {
+      count.value = this.gateways.length;
+    }
+    return this.gateways.slice();
+  },
+
+  getDnses: function(count) {
+    if (count) {
+      count.value = this.dnses.length;
+    }
+    return this.dnses.slice();
+  }
+};
+
+// nsIEthernetManager
+
+/*
+ *  Network state transition diagram
+ *
+ *   ----------  enable  ---------  connect   -----------   disconnect   --------------
+ *  | Disabled | -----> | Enabled | -------> | Connected | <----------> | Disconnected |
+ *   ----------          ---------            -----------    connect     --------------
+ *       ^                  |                      |                           |
+ *       |     disable      |                      |                           |
+ *       -----------------------------------------------------------------------
+ */
+
+function EthernetManager() {
+  debug("EthernetManager start");
+
+  // Interface list.
+  this.ethernetInterfaces = {};
+
+  // Used to memorize last connection information.
+  this.lastStaticConfig = {};
+
+  Services.obs.addObserver(this, "xpcom-shutdown", false);
+}
+
+EthernetManager.prototype = {
+  classID: Components.ID("a96441dd-36b3-4f7f-963b-2c032e28a039"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIEthernetManager]),
+
+  ethernetInterfaces: null,
+  lastStaticConfig: null,
+
+  observer: function(subject, topic, data) {
+    switch (topic) {
+      case "xpcom-shutdown":
+        debug("xpcom-shutdown");
+
+        this._shutdown();
+
+        Services.obs.removeObserver(this, "xpcom-shutdown");
+        break;
+    }
+  },
+
+  _shutdown: function() {
+    debug("shuting down.");
+    (function onRemove(ifnameList) {
+      if (!ifnameList.length) {
+        return;
+      }
+
+      let ifname = ifnameList.shift();
+      this.removeInterface(ifname, { notify: onRemove.bind(this, ifnameList) });
+    }).call(this, Object.keys(this.ethernetInterfaces));
+  },
+
+  get interfaceList() {
+    return Object.keys(this.ethernetInterfaces);
+  },
+
+  scan: function(callback) {
+    debug("scan");
+
+    gNetworkService.getInterfaces(function(success, list) {
+      let ethList = [];
+
+      if (!success) {
+        if (callback) {
+          callback.notify(ethList);
+        }
+        return;
+      }
+
+      for (let i = 0; i < list.length; i++) {
+        debug("Found interface " + list[i]);
+        if (!list[i].startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
+          continue;
+        }
+        ethList.push(list[i]);
+      }
+
+      if (callback) {
+        callback.notify(ethList);
+      }
+    });
+  },
+
+  addInterface: function(ifname, callback) {
+    debug("addInterfaces " + ifname);
+
+    if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
+      if (callback) {
+        callback.notify(false, "Invalid interface.");
+      }
+      return;
+    }
+
+    if (this.ethernetInterfaces[ifname]) {
+      if (callback) {
+        callback.notify(true, "Interface already exists.");
+      }
+      return;
+    }
+
+    gNetworkService.getInterfaceConfig(ifname, function(success, result) {
+      if (!success) {
+        if (callback) {
+          callback.notify(false, "Netd error.");
+        }
+        return;
+      }
+
+      // Since the operation may still succeed with an invalid interface name,
+      // check the mac address as well.
+      if (result.macAddr == INTERFACE_MACADDR_NULL) {
+        if (callback) {
+          callback.notify(false, "Interface not found.");
+        }
+        return;
+      }
+
+      this.ethernetInterfaces[ifname] = new EthernetInterface({
+        state:        result.link == NETWORK_INTERFACE_UP ?
+                        Ci.nsINetworkInterface.NETWORK_STATE_DISABLED :
+                        Ci.nsINetworkInterface.NETWORK_STATE_ENABLED,
+        name:         ifname,
+        type:         Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET,
+        ip:           result.ip,
+        prefixLength: result.prefix,
+        ipMode:       IP_MODE_DHCP
+      });
+
+      // Register the interface to NetworkManager.
+      gNetworkManager.registerNetworkInterface(this.ethernetInterfaces[ifname]);
+
+      debug("Add interface " + ifname + " success with " +
+            JSON.stringify(this.ethernetInterfaces[ifname]));
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this));
+  },
+
+  removeInterface: function(ifname, callback) {
+    debug("removeInterface");
+
+    if (!ifname || !ifname.startsWith(ETHERNET_NETWORK_IFACE_PREFIX)) {
+      if (callback) {
+        callback.notify(false, "Invalid interface.");
+      }
+      return;
+    }
+
+    if (!this.ethernetInterfaces[ifname]) {
+      if (callback) {
+        callback.notify(true, "Interface does not exist.");
+      }
+      return;
+    }
+
+    // Make sure interface is disable before removing.
+    this.disable(ifname, { notify: function(success, message) {
+      // Unregister the interface from NetworkManager and also remove it from
+      // the interface list.
+      gNetworkManager.unregisterNetworkInterface(this.ethernetInterfaces[ifname]);
+      delete this.ethernetInterfaces[ifname];
+
+      debug("Remove interface " + ifname + " success.");
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this)});
+  },
+
+  updateInterfaceConfig: function(ifname, config, callback) {
+    debug("interfaceConfigUpdate with " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      if (!config) {
+        if (callback) {
+          callback.notify(false, "No config to update.");
+        }
+        return;
+      }
+
+      // Network state can not be modified externally.
+      if (config.state) {
+        delete config.state;
+      }
+
+      let currentIpMode = iface.ipMode;
+
+      // Update config.
+      this.ethernetInterfaces[iface.name].updateConfig(config);
+
+      // Do not automatically re-connect if the interface is not in connected
+      // state.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+        return;
+      }
+
+      let newIpMode = this.ethernetInterfaces[iface.name].ipMode;
+      if (newIpMode == IP_MODE_STATIC) {
+        this._setStaticIP(iface.name, callback);
+        return;
+      }
+      if ((currentIpMode == IP_MODE_STATIC) && (newIpMode == IP_MODE_DHCP)) {
+        gNetworkService.stopDhcp(iface.name);
+        // Clear the current network settings before do dhcp request, otherwise
+        // dhcp settings could fail.
+        this.disconnect(iface.name, { notify: function(success, message) {
+          if (!success) {
+            if (callback) {
+              callback.notify("Disconnect failed.");
+            }
+            return;
+          }
+          this._runDhcp(iface.name, callback);
+        }.bind(this) });
+        return;
+      }
+
+      if (callback) {
+        callback.notify(true, "ok");
+      }
+    }.bind(this));
+  },
+
+  enable: function(ifname, callback) {
+    debug("enable with " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can be only enabled in the state of disabled.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) {
+        if (callback) {
+          callback.notify(true, "already enabled.");
+        }
+        return;
+      }
+
+      let ips = {};
+      let prefixLengths = {};
+      iface.getAddresses(ips, prefixLengths);
+      let config = { ifname: iface.name,
+                     ip:     ips.value[0],
+                     prefix: prefixLengths.value[0],
+                     link:   NETWORK_INTERFACE_UP };
+      gNetworkService.setInterfaceConfig(config, function(success) {
+        if (!success) {
+          if (callback) {
+            callback.notify(false, "Netd Error.");
+          }
+          return;
+        }
+
+        this.ethernetInterfaces[iface.name].updateConfig({
+          state: Ci.nsINetworkInterface.NETWORK_STATE_ENABLED
+        });
+
+        debug("Interface " + iface.name + " enable success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  disable: function(ifname, callback) {
+    debug("disable with " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED) {
+        if (callback) {
+          callback.notify(true, "Interface is already disabled.");
+        }
+        return;
+      }
+
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        gNetworkService.stopDhcp(iface.name);
+      }
+
+      let ips = {};
+      let prefixLengths = {};
+      iface.getAddresses(ips, prefixLengths);
+      let config = { ifname: iface.name,
+                     ip:     ips.value[0],
+                     prefix: prefixLengths.value[0],
+                     link:   NETWORK_INTERFACE_DOWN };
+      gNetworkService.setInterfaceConfig(config, function(success) {
+        if (!success) {
+          if (callback) {
+            callback.notify(false, "Netd Error.");
+          }
+          return;
+        }
+
+        this.ethernetInterfaces[iface.name].updateConfig({
+          state: Ci.nsINetworkInterface.NETWORK_STATE_DISABLED
+        });
+
+        debug("Disable interface " + iface.name + " success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  connect: function(ifname, callback) {
+    debug("connect wtih " + ifname);
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can only be connected in the state of enabled or
+      // disconnected.
+      if (iface.state == Ci.nsINetworkInterface.NETWORK_STATE_DISABLED ||
+          iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "Interface " + ifname + " is not available or "
+                                 + " already connected.");
+        }
+        return;
+      }
+
+      if (iface.ipMode == IP_MODE_DHCP) {
+        this._runDhcp(iface.name, callback);
+        return;
+      }
+
+      if (iface.ipMode == IP_MODE_STATIC) {
+        if (this._checkConfigNull(iface) && this.lastStaticConfig[iface.name]) {
+          debug("connect with lastStaticConfig " +
+                JSON.stringify(this.lastStaticConfig[iface.name]));
+          this.ethernetInterfaces[iface.name].updateConfig(
+            this.lastStaticConfig[iface.name]);
+        }
+        this._setStaticIP(iface.name, callback);
+        return;
+      }
+
+      if (callback) {
+        callback.notify(false, "Ip mode is wrong or not set.");
+      }
+    }.bind(this));
+  },
+
+  disconnect: function(ifname, callback) {
+    debug("disconnect");
+
+    this._ensureIfname(ifname, callback, function(iface) {
+      // Interface can be only disconnected in the state of connected.
+      if (iface.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+        if (callback) {
+          callback.notify(true, "interface is already disconnected");
+        }
+        return;
+      }
+
+      let config = { ifname: iface.name,
+                     ip:     INTERFACE_IPADDR_NULL,
+                     prefix: INTERFACE_PREFIX_NULL,
+                     link:   NETWORK_INTERFACE_UP };
+      gNetworkService.setInterfaceConfig(config, function(success) {
+        if (!success) {
+          if (callback) {
+            callback.notify(false, "Netd error.");
+          }
+          return;
+        }
+
+        // Stop dhcp daemon.
+        gNetworkService.stopDhcp(iface.name);
+
+        this.ethernetInterfaces[iface.name].updateConfig({
+          state:        Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
+          ip:           INTERFACE_IPADDR_NULL,
+          prefixLength: INTERFACE_PREFIX_NULL,
+          gateway:      INTERFACE_GATEWAY_NULL
+        });
+
+        Services.obs.notifyObservers(this.ethernetInterfaces[iface.name],
+                                     TOPIC_INTERFACE_STATE_CHANGED,
+                                     null);
+
+        debug("Disconnect interface " + iface.name + " success.");
+
+        if (callback) {
+          callback.notify(true, "ok");
+        }
+      }.bind(this));
+    }.bind(this));
+  },
+
+  _checkConfigNull: function(iface) {
+    let ips = {};
+    let prefixLengths = {};
+    let gateways = iface.getGateways();
+    iface.getAddresses(ips, prefixLengths);
+
+    if (ips.value[0] == INTERFACE_IPADDR_NULL &&
+        prefixLengths.value[0] == INTERFACE_PREFIX_NULL &&
+        gateways[0] == INTERFACE_GATEWAY_NULL) {
+      return true;
+    }
+
+    return false;
+  },
+
+  _ensureIfname: function(ifname, callback, func) {
+    // If no given ifname, use the default one.
+    if (!ifname) {
+      ifname = DEFAULT_ETHERNET_NETWORK_IFACE;
+    }
+
+    let iface = this.ethernetInterfaces[ifname];
+    if (!iface) {
+      if (callback) {
+        callback.notify(true, "Interface " + ifname + " is not available.");
+      }
+      return;
+    }
+
+    func.call(this, iface);
+  },
+
+  _runDhcp: function(ifname, callback) {
+    debug("runDhcp with " + ifname);
+
+    if (!this.ethernetInterfaces[ifname]) {
+      callback.notify(false, "Invalid interface.");
+      return
+    }
+
+    gNetworkService.runDhcp(ifname, function(success, result) {
+      if (!success) {
+        callback.notify(false, "Dhcp failed.");
+        return;
+      }
+
+      debug("Dhcp success with " + JSON.stringify(result));
+
+      // Clear last static network information when connecting with dhcp mode.
+      if (this.lastStaticConfig[ifname]) {
+        this.lastStaticConfig[ifname] = null;
+      }
+
+      this.ethernetInterfaces[ifname].updateConfig({
+        state:        Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED,
+        ip:           result.ip,
+        gateway:      result.gateway,
+        prefixLength: result.prefix,
+        dnses:        [result.dns1, result.dns2]
+      });
+
+      Services.obs.notifyObservers(this.ethernetInterfaces[ifname],
+                                   TOPIC_INTERFACE_STATE_CHANGED,
+                                   null);
+
+      debug("Connect interface " + ifname + "with dhcp success.");
+
+      callback.notify(true, "ok");
+    }.bind(this));
+  },
+
+  _setStaticIP: function(ifname, callback) {
+    let iface = this.ethernetInterfaces[ifname];
+    if (!iface) {
+      callback.notify(false, "Invalid interface.");
+      return;
+    }
+
+    let ips = {};
+    let prefixLengths = {};
+    iface.getAddresses(ips, prefixLengths);
+
+    let config = { ifname: iface.name,
+                   ip:     ips.value[0],
+                   prefix: prefixLengths.value[0],
+                   link:   NETWORK_INTERFACE_UP };
+    gNetworkService.setInterfaceConfig(config, function(success) {
+      if (!success) {
+        callback.notify(false, "Netd Error.");
+        return;
+      }
+
+      // Keep the lastest static network information.
+      let ips = {};
+      let prefixLengths = {};
+      let gateways = iface.getGateways();
+      iface.getAddresses(ips, prefixLengths);
+
+      this.lastStaticConfig[iface.name] = {
+        ip:           ips.value[0],
+        prefixLength: prefixLengths.value[0],
+        gateway:      gateways[0]
+      };
+
+      this.ethernetInterfaces[ifname].updateConfig({
+        state: Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED,
+      });
+
+      Services.obs.notifyObservers(this.ethernetInterfaces[ifname],
+                                   TOPIC_INTERFACE_STATE_CHANGED,
+                                   null);
+
+      debug("Connect interface " + ifname + "with static ip success.");
+
+      callback.notify(true, "ok");
+    }.bind(this));
+  },
+}
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EthernetManager]);
new file mode 100644
--- /dev/null
+++ b/dom/network/src/EthernetManager.manifest
@@ -0,0 +1,2 @@
+component {a96441dd-36b3-4f7f-963b-2c032e28a039} EthernetManager.js
+contract @mozilla.org/ethernetManager;1 {a96441dd-36b3-4f7f-963b-2c032e28a039}
--- a/dom/network/src/moz.build
+++ b/dom/network/src/moz.build
@@ -39,16 +39,18 @@ EXTRA_COMPONENTS += [
 ]
 
 EXTRA_PP_COMPONENTS += [
     'TCPSocket.js',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     EXTRA_COMPONENTS += [
+        'EthernetManager.js',
+        'EthernetManager.manifest',
         'NetworkStatsManager.js',
         'NetworkStatsManager.manifest',
         'NetworkStatsServiceProxy.js',
         'NetworkStatsServiceProxy.manifest',
     ]
     EXPORTS.mozilla.dom.network += [
         'NetUtils.h',
     ]
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/head.js
@@ -0,0 +1,551 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
+
+const ETHERNET_MANAGER_CONTRACT_ID = "@mozilla.org/ethernetManager;1";
+
+const INTERFACE_UP = "UP";
+const INTERFACE_DOWN = "DOWN";
+
+let gTestSuite = (function() {
+  let suite = {};
+
+  // Private member variables of the returned object |suite|.
+  let ethernetManager = SpecialPowers.Cc[ETHERNET_MANAGER_CONTRACT_ID]
+                                     .getService(SpecialPowers.Ci.nsIEthernetManager);
+  let pendingEmulatorShellCount = 0;
+
+  /**
+   * Send emulator shell command with safe guard.
+   *
+   * We should only call |finish()| after all emulator command transactions
+   * end, so here comes with the pending counter.  Resolve when the emulator
+   * gives positive response, and reject otherwise.
+   *
+   * Fulfill params: an array of emulator response lines.
+   * Reject params: an array of emulator response lines.
+   *
+   * @param command
+   *        A string command to be passed to emulator through its telnet console.
+   *
+   * @return A deferred promise.
+   */
+  function runEmulatorShellSafe(command) {
+    let deferred = Promise.defer();
+
+    ++pendingEmulatorShellCount;
+    runEmulatorShell(command, function(aResult) {
+      --pendingEmulatorShellCount;
+
+      ok(true, "Emulator shell response: " + JSON.stringify(aResult));
+      if (Array.isArray(aResult)) {
+        deferred.resolve(aResult);
+      } else {
+        deferred.reject(aResult);
+      }
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Get the system network conifg by the given interface name.
+   *
+   * Use shell command 'netcfg' to get the list of network cofig.
+   *
+   * Fulfill params: An object of { name, flag, ip }
+   *
+   * @parm ifname
+   *       Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function getNetworkConfig(ifname) {
+    return runEmulatorShellSafe(['netcfg'])
+      .then(result => {
+        // Sample 'netcfg' output:
+        //
+        // lo       UP                                   127.0.0.1/8   0x00000049 00:00:00:00:00:00
+        // eth0     UP                                   10.0.2.15/24  0x00001043 52:54:00:12:34:56
+        // eth1     DOWN                                   0.0.0.0/0   0x00001002 52:54:00:12:34:57
+        // rmnet1   DOWN                                   0.0.0.0/0   0x00001002 52:54:00:12:34:59
+
+        let config;
+
+        for (let i = 0; i < result.length; i++) {
+          let tokens = result[i].split(/\s+/);
+          let name = tokens[0];
+          let flag = tokens[1];
+          let ip = tokens[2].split(/\/+/)[0];
+          if (name == ifname) {
+            config = { name: name, flag: flag, ip: ip };
+            break;
+          }
+        }
+
+        return config;
+      });
+  }
+
+  /**
+   * Get the ip assigned by dhcp server of a given interface name.
+   *
+   * Get the ip from android property 'dhcp.[ifname].ipaddress'.
+   *
+   * Fulfill params: A string of ip address.
+   *
+   * @parm ifname
+   *       Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function getDhcpIpAddr(ifname) {
+    return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.ipaddress'])
+      .then(function(ipAddr) {
+        return ipAddr[0];
+      });
+  }
+
+  /**
+   * Get the gateway assigned by dhcp server of a given interface name.
+   *
+   * Get the ip from android property 'dhcp.[ifname].gateway'.
+   *
+   * Fulfill params: A string of gateway.
+   *
+   * @parm ifname
+   *       Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function getDhcpGateway(ifname) {
+    return runEmulatorShellSafe(['getprop', 'dhcp.' + ifname + '.gateway'])
+      .then(function(gateway) {
+        return gateway[0];
+      });
+  }
+
+  /**
+   * Get the default route.
+   *
+   * Use shell command 'ip route' to get the default of device.
+   *
+   * Fulfill params: An array of { name, gateway }
+   *
+   * @return A deferred promise.
+   */
+  function getDefaultRoute() {
+    return runEmulatorShellSafe(['ip', 'route'])
+      .then(result => {
+        // Sample 'ip route' output:
+        //
+        // 10.0.2.0/24 dev eth0  proto kernel  scope link  src 10.0.2.15
+        // default via 10.0.2.2 dev eth0  metric 2
+
+        let routeInfo = [];
+
+        for (let i = 0; i < result.length; i++) {
+          if (!result[i].match('default')) {
+            continue;
+          }
+
+          let tokens = result[i].split(/\s+/);
+          let name = tokens[4];
+          let gateway = tokens[2];
+          routeInfo.push({ name: name, gateway: gateway });
+        }
+
+        return routeInfo;
+      });
+  }
+
+  /**
+   * Check a specific interface is enabled or not.
+   *
+   * @parm ifname
+   *       Interface name.
+   * @parm enabled
+   *       A boolean value used to check interface is disable or not.
+   *
+   * @return A deferred promise.
+   */
+  function checkInterfaceIsEnabled(ifname, enabled) {
+    return getNetworkConfig(ifname)
+      .then(function(config) {
+        if (enabled) {
+          is(config.flag, INTERFACE_UP, "Interface is enabled as expectation.");
+        } else {
+          is(config.flag, INTERFACE_DOWN, "Interface is disabled as expectation.");
+        }
+      });
+  }
+
+  /**
+   * Check the ip of a specific interface is equal to given ip or not.
+   *
+   * @parm ifname
+   *       Interface name.
+   * @parm ip
+   *       Given ip address.
+   *
+   * @return A deferred promise.
+   */
+  function checkInterfaceIpAddr(ifname, ip) {
+    return getNetworkConfig(ifname)
+      .then(function(config) {
+        is(config.ip, ip, "IP is right as expectation.");
+      });
+  }
+
+  /**
+   * Check the default gateway of a specific interface is equal to given gateway
+   * or not.
+   *
+   * @parm ifname
+   *       Interface name.
+   * @parm gateway
+   *       Given gateway.
+   *
+   * @return A deferred promise.
+   */
+  function checkDefaultRoute(ifname, gateway) {
+    return getDefaultRoute()
+      .then(function(routeInfo) {
+        for (let i = 0; i < routeInfo.length; i++) {
+          if (routeInfo[i].name == ifname) {
+            is(routeInfo[i].gateway, gateway,
+               "Default gateway is right as expectation.");
+            return true;
+          }
+        }
+
+        if (!gateway) {
+          ok(true, "Default route is cleared.");
+          return true;
+        }
+
+        return false;
+      });
+  }
+
+  /**
+   * Check the length of interface list in EthernetManager is equal to given
+   * length or not.
+   *
+   * @parm length
+   *       Given length.
+   */
+  function checkInterfaceListLength(length) {
+    let list = ethernetManager.interfaceList;
+    is(length, list.length, "List length is equal as expectation.");
+  }
+
+  /**
+   * Check the given interface exists on device or not.
+   *
+   * @parm ifname
+   *       Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function checkInterfaceExist(ifname) {
+    return scanInterfaces()
+      .then(list => {
+        let index = list.indexOf(ifname);
+        if (index < 0) {
+          throw "Interface " + ifname + " not found.";
+        }
+
+        ok(true, ifname + " exists.")
+      });
+  }
+
+  /**
+   * Scan for available ethernet interfaces.
+   *
+   * Fulfill params: A list of available interfaces found in device.
+   *
+   * @return A deferred promise.
+   */
+  function scanInterfaces() {
+    let deferred = Promise.defer();
+
+    ethernetManager.scan(function onScan(list) {
+      deferred.resolve(list);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Add an interface into interface list.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function addInterface(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.addInterface(ifname, function onAdd(success, message) {
+      ok(success, "Add interface " + ifname + " success.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Remove an interface form the interface list.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function removeInterface(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.removeInterface(ifname, function onRemove(success, message) {
+      ok(success, "Remove interface " + ifname + " success.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Enable networking of an interface in the interface list.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function enableInterface(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.enable(ifname, function onEnable(success, message) {
+      ok(success, "Enable interface " + ifname + " success.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Disable networking of an interface in the interface list.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function disableInterface(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.disable(ifname, function onDisable(success, message) {
+      ok(success, "Disable interface " + ifname + " success.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Make an interface connect to network.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function makeInterfaceConnect(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.connect(ifname, function onConnect(success, message) {
+      ok(success, "Interface " + ifname + " is connected successfully.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Make an interface disconnect to network.
+   *
+   * Fulfill params: A boolean value indicates success or not.
+   *
+   * @param ifname
+   *        Interface name.
+   *
+   * @return A deferred promise.
+   */
+  function makeInterfaceDisconnect(ifname) {
+    let deferred = Promise.defer();
+
+    ethernetManager.disconnect(ifname, function onDisconnect(success, message) {
+      ok(success, "Interface " + ifname + " is disconnected successfully.");
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Update the config the an interface in the interface list.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param config
+   *        .ip: ip address.
+   *        .prefixLength: mask length.
+   *        .gateway: gateway.
+   *        .dnses: dnses.
+   *        .httpProxyHost: http proxy host.
+   *        .httpProxyPort: http porxy port.
+   *        .usingDhcp: an boolean value indicates using dhcp or not.
+   *
+   * @return A deferred promise.
+   */
+  function updateInterfaceConfig(ifname, config) {
+    let deferred = Promise.defer();
+
+    ethernetManager.updateInterfaceConfig(ifname, config,
+                                          function onUpdated(success, message) {
+      ok(success, "Interface " + ifname + " config is updated successfully " +
+                  " with " + JSON.stringify(config));
+      is(message, "ok", "Message is as expectation.");
+
+      deferred.resolve(success);
+    });
+
+    return deferred.promise;
+  }
+
+  /**
+   * Wait for timeout.
+   *
+   * @param timeout
+   *        Time in ms.
+   *
+   * @return A deferred promise.
+   */
+  function waitForTimeout(timeout) {
+    let deferred = Promise.defer();
+
+    setTimeout(function() {
+      ok(true, "waitForTimeout " + timeout);
+      deferred.resolve();
+    }, timeout);
+
+    return deferred.promise;
+  }
+
+  /**
+   * Wait for default route of a specific interface being set and
+   * check.
+   *
+   * @param ifname
+   *        Interface name.
+   * @param gateway
+   *        Target gateway.
+   *
+   * @return A deferred promise.
+   */
+  function waitForDefaultRouteSet(ifname, gateway) {
+    return gTestSuite.waitForTimeout(500)
+      .then(() => gTestSuite.checkDefaultRoute(ifname, gateway))
+      .then(success => {
+        if (success) {
+          ok(true, "Default route is set as expectation " + gateway);
+          return;
+        }
+
+        ok(true, "Default route is not set yet, check again. " + success);
+        return waitForDefaultRouteSet(ifname, gateway);
+      });
+  }
+
+  //---------------------------------------------------
+  // Public test suite functions
+  //---------------------------------------------------
+  suite.scanInterfaces = scanInterfaces;
+  suite.addInterface = addInterface;
+  suite.removeInterface = removeInterface;
+  suite.enableInterface = enableInterface;
+  suite.disableInterface = disableInterface;
+  suite.makeInterfaceConnect = makeInterfaceConnect;
+  suite.makeInterfaceDisconnect = makeInterfaceDisconnect;
+  suite.updateInterfaceConfig = updateInterfaceConfig;
+  suite.getDhcpIpAddr = getDhcpIpAddr;
+  suite.getDhcpGateway = getDhcpGateway;
+  suite.checkInterfaceExist = checkInterfaceExist;
+  suite.checkInterfaceIsEnabled = checkInterfaceIsEnabled;
+  suite.checkInterfaceIpAddr = checkInterfaceIpAddr;
+  suite.checkDefaultRoute = checkDefaultRoute;
+  suite.checkInterfaceListLength = checkInterfaceListLength;
+  suite.waitForTimeout = waitForTimeout;
+  suite.waitForDefaultRouteSet = waitForDefaultRouteSet;
+
+  /**
+   * End up the test run.
+   *
+   * Wait until all pending emulator shell commands are done and then |finish|
+   * will be called in the end.
+   */
+  function cleanUp() {
+    waitFor(finish, function() {
+      return pendingEmulatorShellCount === 0;
+    });
+  }
+
+  /**
+   * Common test routine.
+   *
+   * Start a test with the given test case chain. The test environment will be
+   * settled down before the test. After the test, all the affected things will
+   * be restored.
+   *
+   * @param aTestCaseChain
+   *        The test case entry point, which can be a function or a promise.
+   *
+   * @return A deferred promise.
+   */
+  suite.doTest = function(aTestCaseChain) {
+    return Promise.resolve()
+      .then(aTestCaseChain)
+      .then(function onresolve() {
+        cleanUp();
+      }, function onreject(aReason) {
+        ok(false, 'Promise rejects during test' + (aReason ? '(' + aReason + ')' : ''));
+        cleanUp();
+      });
+  };
+
+  return suite;
+})();
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/manifest.ini
@@ -0,0 +1,15 @@
+[DEFAULT]
+b2g = true
+browser = false
+qemu = true
+
+[test_ethernet_add_interface.js]
+[test_ethernet_remove_interface.js]
+[test_ethernet_enable.js]
+[test_ethernet_disable.js]
+[test_ethernet_connect_with_dhcp.js]
+[test_ethernet_connect_with_static_ip.js]
+[test_ethernet_reconnect_with_dhcp.js]
+[test_ethernet_reconnect_with_static_ip.js]
+[test_ethernet_ip_mode_change.js]
+[test_ethernet_disconnect.js]
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_add_interface.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceListLength(0))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceListLength(1))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_connect_with_dhcp.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+function checkDhcpResult(ifname) {
+  return gTestSuite.getDhcpIpAddr(ifname)
+    .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
+    .then(() => gTestSuite.getDhcpGateway(ifname))
+    .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_connect_with_static_ip.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+let staticConfig = {
+  ip: "1.2.3.4",
+  gateway: "1.2.3.5",
+  prefixLength: 24,
+  dnses: ["1.2.3.6"],
+  ipMode: "static"
+};
+
+function checkStaticResult(ifname) {
+  return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
+    .then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_disable.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, false))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_disconnect.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+const INTERFACE_IP_NONE = "0.0.0.0";
+
+function checkIpAddrIsReset(ifname) {
+  return gTestSuite.checkInterfaceIpAddr(ifname, INTERFACE_IP_NONE)
+    .then(() => gTestSuite.checkDefaultRoute(ifname));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkIpAddrIsReset(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_enable.js
@@ -0,0 +1,17 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceIsEnabled(ETHERNET_INTERFACE_NAME, true))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_ip_mode_change.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+let staticConfig = {
+  ip: "1.2.3.4",
+  gateway: "1.2.3.5",
+  prefixLength: 24,
+  dnses: ["1.2.3.6"],
+  ipMode: "static"
+};
+
+function checkStaticResult(ifname) {
+  return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
+    .then(() => gTestSuite.waitForDefaultRouteSet(ifname, staticConfig.gateway));
+}
+
+function checkDhcpResult(ifname) {
+  return gTestSuite.getDhcpIpAddr(ifname)
+    .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
+    .then(() => gTestSuite.getDhcpGateway(ifname))
+    .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
+    .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, { ipMode: "dhcp"}))
+    .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_reconnect_with_dhcp.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+function checkDhcpResult(ifname) {
+  return gTestSuite.getDhcpIpAddr(ifname)
+    .then(ip => gTestSuite.checkInterfaceIpAddr(ifname, ip))
+    .then(() => gTestSuite.getDhcpGateway(ifname))
+    .then(gateway => gTestSuite.waitForDefaultRouteSet(ifname, gateway));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkDhcpResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_reconnect_with_static_ip.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+let staticConfig = {
+  ip: "1.2.3.4",
+  gateway: "1.2.3.5",
+  prefixLength: 24,
+  dnses: ["1.2.3.6"],
+  ipMode: "static"
+};
+
+function checkStaticResult(ifname) {
+  return gTestSuite.checkInterfaceIpAddr(ifname, staticConfig.ip)
+    .then(() => gTestSuite.checkDefaultRoute(ifname, staticConfig.gateway));
+}
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.enableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.updateInterfaceConfig(ETHERNET_INTERFACE_NAME, staticConfig))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceConnect(ETHERNET_INTERFACE_NAME))
+    .then(() => checkStaticResult(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.makeInterfaceDisconnect(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.disableInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME));
+});
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/network/tests/marionette/test_ethernet_remove_interface.js
@@ -0,0 +1,16 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+const ETHERNET_INTERFACE_NAME = "eth1";
+
+gTestSuite.doTest(function() {
+  return Promise.resolve()
+    .then(() => gTestSuite.checkInterfaceExist(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.addInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceListLength(1))
+    .then(() => gTestSuite.removeInterface(ETHERNET_INTERFACE_NAME))
+    .then(() => gTestSuite.checkInterfaceListLength(0));
+});
\ No newline at end of file
--- a/dom/nfc/tests/marionette/head.js
+++ b/dom/nfc/tests/marionette/head.js
@@ -40,16 +40,28 @@ let emulator = (function() {
     this.run(cmd, function(result) {
       is(result.pop(), 'OK', 'check activation of RE' + re);
       deferred.resolve();
     });
 
     return deferred.promise;
   };
 
+ function deactivate() {
+    let deferred = Promise.defer();
+    let cmd = 'nfc nci rf_intf_deactivate_ntf';
+
+    this.run(cmd, function(result) {
+      is(result.pop(), 'OK', 'check deactivate');
+      deferred.resolve();
+    });
+
+    return deferred.promise;
+  };
+
   function notifyDiscoverRE(re, type) {
     let deferred = Promise.defer();
     let cmd = 'nfc nci rf_discover_ntf ' + re + ' ' + type;
 
     this.run(cmd, function(result) {
       is(result.pop(), 'OK', 'check discovery of RE' + re);
       deferred.resolve();
     });
@@ -83,16 +95,17 @@ let emulator = (function() {
     });
 
     return deferred.promise;
   };
 
   return {
     run: run,
     activateRE: activateRE,
+    deactivate: deactivate,
     notifyDiscoverRE: notifyDiscoverRE,
     setTagData: setTagData,
     snepPutNdef: snepPutNdef
   };
 }());
 
 function toggleNFC(enabled) {
   let deferred = Promise.defer();
--- a/dom/nfc/tests/marionette/manifest.ini
+++ b/dom/nfc/tests/marionette/manifest.ini
@@ -2,13 +2,14 @@
 b2g=true
 browser=false
 qemu=true
 
 [test_ndef.js]
 [test_nfc_enabled.js]
 [test_nfc_manager_tech_discovered.js]
 [test_nfc_manager_tech_discovered_ndef.js]
+[test_nfc_manager_tech_lost.js]
 [test_nfc_peer.js]
 [test_nfc_peer_sendndef.js]
 [test_nfc_tag.js]
 [test_nfc_checkP2PRegistration.js]
 [test_nfc_error_messages.js]
new file mode 100644
--- /dev/null
+++ b/dom/nfc/tests/marionette/test_nfc_manager_tech_lost.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 30000;
+MARIONETTE_HEAD_JS = 'head.js';
+
+function handleTechnologyLost(msg) {
+  log('Received \'nfc-manager-tech-lost\'');
+  is(msg.type, 'techLost', 'check for correct message type');
+
+  toggleNFC(false).then(runNextTest)
+}
+
+function handleTechnologyDiscoveredRE0(msg) {
+  log('Received \'nfc-manager-tech-discovered\'');
+  is(msg.type, 'techDiscovered', 'check for correct message type');
+  is(msg.techList[0], 'P2P', 'check for correct tech type');
+
+  emulator.deactivate();
+}
+
+function testTechLost() {
+  log('Running \'testTechLost\'');
+  window.navigator.mozSetMessageHandler(
+    'nfc-manager-tech-discovered', handleTechnologyDiscoveredRE0);
+  window.navigator.mozSetMessageHandler(
+    'nfc-manager-tech-lost', handleTechnologyLost);
+
+  toggleNFC(true).then(() => emulator.activateRE(0));
+}
+
+let tests = [
+  testTechLost
+];
+
+SpecialPowers.pushPermissions(
+  [{'type': 'nfc-manager', 'allow': true, context: document}], runTests);
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -8,19 +8,19 @@ const {classes: Cc, interfaces: Ci, util
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
 
 const NETWORKMANAGER_CONTRACTID = "@mozilla.org/network/manager;1";
 const NETWORKMANAGER_CID =
-  Components.ID("{33901e46-33b8-11e1-9869-f46d04d25bcc}");
+  Components.ID("{1ba9346b-53b5-4660-9dc6-58f0b258d0a6}");
 
-const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_WIFI;
+const DEFAULT_PREFERRED_NETWORK_TYPE = Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET;
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
   return Cc["@mozilla.org/parentprocessmessagemanager;1"]
          .getService(Ci.nsIMessageBroadcaster);
 });
@@ -271,17 +271,18 @@ NetworkManager.prototype = {
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN) {
               this.removeSecondaryDefaultRoute(network);
             }
 #endif
             // Remove routing table in /proc/net/route
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
               gNetworkService.resetRoutingTable(network);
 #ifdef MOZ_B2G_RIL
-            } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
+            } else if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
+                       network.type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) {
               gNetworkService.removeDefaultRoute(network);
 #endif
             }
 
             // Abort ongoing captive portal detection on the wifi interface
             CaptivePortalDetectionHelper
               .notify(CaptivePortalDetectionHelper.EVENT_DISCONNECT, network);
             this.setAndConfigureActive();
@@ -362,17 +363,19 @@ NetworkManager.prototype = {
         return interfaces;
       }
     }
   },
 
   getNetworkId: function(network) {
     let id = "device";
 #ifdef MOZ_B2G_RIL
-    if (this.isNetworkTypeMobile(network.type)) {
+    if (this.isNetworkTypeEthernet(network.type)) {
+      id = network.name.substring(3);
+    } else if (this.isNetworkTypeMobile(network.type)) {
       if (!(network instanceof Ci.nsIRilNetworkInterface)) {
         throw Components.Exception("Mobile network not an nsIRilNetworkInterface",
                                    Cr.NS_ERROR_INVALID_ARG);
       }
       id = "ril" + network.serviceId;
     }
 #endif
 
@@ -414,27 +417,61 @@ NetworkManager.prototype = {
   },
 
   _manageOfflineStatus: true,
 
   networkInterfaces: null,
 
   _dataDefaultServiceId: null,
 
+  _networkTypePriorityList: [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET,
+                             Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                             Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE],
+  get networkTypePriorityList() {
+    return this._networkTypePriorityList;
+  },
+  set networkTypePriorityList(val) {
+    if (val.length != this._networkTypePriorityList.length) {
+      throw "Priority list length should equal to " +
+            this._networkTypePriorityList.length;
+    }
+
+    // Check if types in new priority list are valid and also make sure there
+    // are no duplicate types.
+    let list = [Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET,
+                Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
+                Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE];
+    while (list.length) {
+      let type = list.shift();
+      if (val.indexOf(type) == -1) {
+        throw "There is missing network type";
+      }
+    }
+
+    this._networkTypePriorityList = val;
+  },
+
+  getPriority: function(type) {
+    if (this._networkTypePriorityList.indexOf(type) == -1) {
+      // 0 indicates the lowest priority.
+      return 0;
+    }
+
+    return this._networkTypePriorityList.length -
+           this._networkTypePriorityList.indexOf(type);
+  },
+
   _preferredNetworkType: DEFAULT_PREFERRED_NETWORK_TYPE,
   get preferredNetworkType() {
     return this._preferredNetworkType;
   },
   set preferredNetworkType(val) {
-#ifdef MOZ_B2G_RIL
     if ([Ci.nsINetworkInterface.NETWORK_TYPE_WIFI,
-         Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE].indexOf(val) == -1) {
-#else
-    if (val != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
-#endif
+         Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE,
+         Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET].indexOf(val) == -1) {
       throw "Invalid network type";
     }
     this._preferredNetworkType = val;
   },
 
   active: null,
   _overriddenActive: null,
 
@@ -456,16 +493,20 @@ NetworkManager.prototype = {
             type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_DUN);
   },
 
   isNetworkTypeMobile: function(type) {
     return (type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
             this.isNetworkTypeSecondaryMobile(type));
   },
 
+  isNetworkTypeEthernet: function(type) {
+    return (type == Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET);
+  },
+
   setExtraHostRoute: function(network) {
     if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS) {
       if (!(network instanceof Ci.nsIRilNetworkInterface)) {
         debug("Network for MMS must be an instance of nsIRilNetworkInterface");
         return;
       }
 
       network = network.QueryInterface(Ci.nsIRilNetworkInterface);
@@ -596,21 +637,33 @@ NetworkManager.prototype = {
       if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
         continue;
       }
 #ifdef MOZ_B2G_RIL
       if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
         defaultDataNetwork = network;
       }
 #endif
-      this.active = network;
       if (network.type == this.preferredNetworkType) {
+        this.active = network;
         debug("Found our preferred type of network: " + network.name);
         break;
       }
+
+      // Initialize the active network with the first connected network.
+      if (!this.active) {
+        this.active = network;
+        continue;
+      }
+
+      // Compare the prioriy between two network types. If found incoming
+      // network with higher priority, replace the active network.
+      if (this.getPriority(this.active.type) < this.getPriority(network.type)) {
+        this.active = network;
+      }
     }
     if (this.active) {
 #ifdef MOZ_B2G_RIL
       // Give higher priority to default data APN than seconary APN.
       // If default data APN is not connected, we still set default route
       // and DNS on seconary APN.
       if (defaultDataNetwork &&
           this.isNetworkTypeSecondaryMobile(this.active.type) &&
@@ -675,29 +728,32 @@ NetworkManager.prototype = {
   },
 #endif
 
   convertConnectionType: function(network) {
     // If there is internal interface change (e.g., MOBILE_MMS, MOBILE_SUPL),
     // the function will return null so that it won't trigger type change event
     // in NetworkInformation API.
     if (network.type != Ci.nsINetworkInterface.NETWORK_TYPE_WIFI &&
-        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE) {
+        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE &&
+        network.type != Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET) {
       return null;
     }
 
     if (network.state == Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED) {
       return CONNECTION_TYPE_NONE;
     }
 
     switch (network.type) {
       case Ci.nsINetworkInterface.NETWORK_TYPE_WIFI:
         return CONNECTION_TYPE_WIFI;
       case Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE:
         return CONNECTION_TYPE_CULLULAR;
+      case Ci.nsINetworkInterface.NETWORK_TYPE_ETHERNET:
+        return CONNECTION_TYPE_ETHERNET;
     }
   },
 
   // nsISettingsServiceCallback
 
   tetheringSettings: {},
 
   initTetheringSettings: function() {
--- a/dom/system/gonk/NetworkManager.manifest
+++ b/dom/system/gonk/NetworkManager.manifest
@@ -1,3 +1,3 @@
 # NetworkManager.js
-component {33901e46-33b8-11e1-9869-f46d04d25bcc} NetworkManager.js
-contract @mozilla.org/network/manager;1 {33901e46-33b8-11e1-9869-f46d04d25bcc}
+component {1ba9346b-53b5-4660-9dc6-58f0b258d0a6} NetworkManager.js
+contract @mozilla.org/network/manager;1 {1ba9346b-53b5-4660-9dc6-58f0b258d0a6}
--- a/dom/system/gonk/NetworkService.js
+++ b/dom/system/gonk/NetworkService.js
@@ -7,17 +7,17 @@
 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/NetUtil.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const NETWORKSERVICE_CONTRACTID = "@mozilla.org/network/service;1";
-const NETWORKSERVICE_CID = Components.ID("{baec696c-c78d-42db-8b44-603f8fbfafb4}");
+const NETWORKSERVICE_CID = Components.ID("{48c13741-aec9-4a86-8962-432011708261}");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkWorker",
                                    "@mozilla.org/network/worker;1",
                                    "nsINetworkWorker");
 
 // 1xx - Requested action is proceeding
 const NETD_COMMAND_PROCEEDING   = 100;
 // 2xx - Requested action has been successfully completed
@@ -537,16 +537,91 @@ NetworkService.prototype = {
     this.controlMessage(params, function(data) {
       let code = data.resultCode;
       let reason = data.resultReason;
       if(DEBUG) debug("updateUpStream result: Code " + code + " reason " + reason);
       callback.updateUpStreamResult(!isError(code), data.curExternalIfname);
     });
   },
 
+  getInterfaces: function(callback) {
+    let params = {
+      cmd: "getInterfaces",
+      isAsync: true
+    };
+
+    this.controlMessage(params, function(data) {
+      if(DEBUG) debug("getInterfaces result: " + JSON.stringify(data));
+      let success = !isError(data.resultCode);
+      callback.getInterfacesResult(success, data.interfaceList);
+    });
+  },
+
+  setInterfaceConfig: function(config, callback) {
+    config.cmd = "setInterfaceConfig";
+    config.isAsync = true;
+
+    this.controlMessage(config, function(data) {
+      if(DEBUG) debug("setInterfaceConfig result: " + JSON.stringify(data));
+      let success = !isError(data.resultCode);
+      callback.setInterfaceConfigResult(success);
+    });
+  },
+
+  getInterfaceConfig: function(ifname, callback) {
+    let params = {
+      cmd: "getInterfaceConfig",
+      ifname: ifname,
+      isAsync: true
+    };
+
+    this.controlMessage(params, function(data) {
+      if(DEBUG) debug("getInterfaceConfig result: " + JSON.stringify(data));
+      let success = !isError(data.resultCode);
+      let result = { ip: data.ipAddr,
+                     prefix: data.maskLength,
+                     link: data.flag,
+                     mac: data.macAddr };
+      callback.getInterfaceConfigResult(success, result);
+    });
+  },
+
+  runDhcp: function(ifname, callback) {
+    let params = {
+      cmd: "runDhcp",
+      ifname: ifname,
+      isBlocking: true
+    };
+
+    this.controlMessage(params, function(data) {
+      if(DEBUG) debug("runDhcp result: " + JSON.stringify(data));
+      let success = data.success;
+      let result = {
+        ip: data.ipAddr,
+        gateway: data.gateway,
+        dns1: data.dns1,
+        dns2: data.dns2,
+        prefix: data.maskLength,
+        server: data.server
+      };
+
+      callback.runDhcpResult(success, result);
+    });
+  },
+
+  stopDhcp: function(ifname) {
+    let params = {
+      cmd: "stopDhcp",
+      ifname: ifname,
+      isAsync: true
+    };
+
+    this.controlMessage(params);
+  },
+
   shutdown: false,
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
       case "xpcom-shutdown":
         debug("NetworkService shutdown");
         this.shutdown = true;
         Services.obs.removeObserver(this, "xpcom-shutdown");
--- a/dom/system/gonk/NetworkService.manifest
+++ b/dom/system/gonk/NetworkService.manifest
@@ -1,3 +1,3 @@
 # NetworkService.js
-component {baec696c-c78d-42db-8b44-603f8fbfafb4} NetworkService.js
-contract @mozilla.org/network/service;1 {baec696c-c78d-42db-8b44-603f8fbfafb4}
+component {48c13741-aec9-4a86-8962-432011708261} NetworkService.js
+contract @mozilla.org/network/service;1 {48c13741-aec9-4a86-8962-432011708261}
--- a/dom/system/gonk/NetworkUtils.cpp
+++ b/dom/system/gonk/NetworkUtils.cpp
@@ -106,17 +106,17 @@ static NetworkParams *gWifiTetheringParm
 
 
 CommandFunc NetworkUtils::sWifiEnableChain[] = {
   NetworkUtils::clearWifiTetherParms,
   NetworkUtils::wifiFirmwareReload,
   NetworkUtils::startAccessPointDriver,
   NetworkUtils::setAccessPoint,
   NetworkUtils::startSoftAP,
-  NetworkUtils::setInterfaceUp,
+  NetworkUtils::setConfig,
   NetworkUtils::tetherInterface,
   NetworkUtils::setIpForwardingEnabled,
   NetworkUtils::tetheringStatus,
   NetworkUtils::startTethering,
   NetworkUtils::setDnsForwarders,
   NetworkUtils::enableNat,
   NetworkUtils::wifiTetheringSuccess
 };
@@ -147,33 +147,33 @@ CommandFunc NetworkUtils::sWifiRetryChai
   NetworkUtils::stopSoftAP,
   NetworkUtils::stopTethering,
 
   // sWifiEnableChain:
   NetworkUtils::wifiFirmwareReload,
   NetworkUtils::startAccessPointDriver,
   NetworkUtils::setAccessPoint,
   NetworkUtils::startSoftAP,
-  NetworkUtils::setInterfaceUp,
+  NetworkUtils::setConfig,
   NetworkUtils::tetherInterface,
   NetworkUtils::setIpForwardingEnabled,
   NetworkUtils::tetheringStatus,
   NetworkUtils::startTethering,
   NetworkUtils::setDnsForwarders,
   NetworkUtils::enableNat,
   NetworkUtils::wifiTetheringSuccess
 };
 
 CommandFunc NetworkUtils::sWifiOperationModeChain[] = {
   NetworkUtils::wifiFirmwareReload,
   NetworkUtils::wifiOperationModeSuccess
 };
 
 CommandFunc NetworkUtils::sUSBEnableChain[] = {
-  NetworkUtils::setInterfaceUp,
+  NetworkUtils::setConfig,
   NetworkUtils::enableNat,
   NetworkUtils::setIpForwardingEnabled,
   NetworkUtils::tetherInterface,
   NetworkUtils::tetheringStatus,
   NetworkUtils::startTethering,
   NetworkUtils::setDnsForwarders,
   NetworkUtils::usbTetheringSuccess
 };
@@ -196,17 +196,17 @@ CommandFunc NetworkUtils::sUSBFailChain[
 
 CommandFunc NetworkUtils::sUpdateUpStreamChain[] = {
   NetworkUtils::cleanUpStream,
   NetworkUtils::createUpStream,
   NetworkUtils::updateUpStreamSuccess
 };
 
 CommandFunc NetworkUtils::sStartDhcpServerChain[] = {
-  NetworkUtils::setInterfaceUp,
+  NetworkUtils::setConfig,
   NetworkUtils::startTethering,
   NetworkUtils::setDhcpServerSuccess
 };
 
 CommandFunc NetworkUtils::sStopDhcpServerChain[] = {
   NetworkUtils::stopTethering,
   NetworkUtils::setDhcpServerSuccess
 };
@@ -235,16 +235,31 @@ CommandFunc NetworkUtils::sNetworkInterf
   NetworkUtils::networkInterfaceAlarmSuccess
 };
 
 CommandFunc NetworkUtils::sSetDnsChain[] = {
   NetworkUtils::setDefaultInterface,
   NetworkUtils::setInterfaceDns
 };
 
+CommandFunc NetworkUtils::sGetInterfacesChain[] = {
+  NetworkUtils::getInterfaceList,
+  NetworkUtils::getInterfacesSuccess
+};
+
+CommandFunc NetworkUtils::sSetInterfaceConfigChain[] = {
+  NetworkUtils::setConfig,
+  NetworkUtils::setInterfaceConfigSuccess
+};
+
+CommandFunc NetworkUtils::sGetInterfaceConfigChain[] = {
+  NetworkUtils::getConfig,
+  NetworkUtils::getInterfaceConfigSuccess
+};
+
 /**
  * Helper function to get the mask from given prefix length.
  */
 static uint32_t makeMask(const uint32_t prefixLength)
 {
   uint32_t mask = 0;
   for (uint32_t i = 0; i < prefixLength; ++i) {
     mask |= (0x80000000 >> i);
@@ -317,16 +332,25 @@ static void join(nsTArray<nsCString>& ar
       CHECK_LENGTH(len, strlen(array[i].get()), maxlen)
       strcat(result, array[i].get());
     }
   }
 
 #undef CHECK_LEN
 }
 
+static void convertUTF8toUTF16(nsTArray<nsCString>& narrow,
+                               nsTArray<nsString>& wide,
+                               uint32_t length)
+{
+  for (uint32_t i = 0; i < length; i++) {
+    wide.AppendElement(NS_ConvertUTF8toUTF16(narrow[i].get()));
+  }
+}
+
 /**
  * Helper function to get network interface properties from the system property table.
  */
 static void getIFProperties(const char* ifname, IFProperties& prop)
 {
   char key[PROPERTY_KEY_MAX];
   snprintf(key, PROPERTY_KEY_MAX - 1, "net.%s.gw", ifname);
   property_get(key, prop.gateway, "");
@@ -681,19 +705,19 @@ void NetworkUtils::setAlarm(CommandChain
                             NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
   snprintf(command, MAX_COMMAND_SIZE - 1, "bandwidth setinterfacealert %s %ld", GET_CHAR(mIfname), GET_FIELD(mThreshold));
 
   doCommand(command, aChain, aCallback);
 }
 
-void NetworkUtils::setInterfaceUp(CommandChain* aChain,
-                                  CommandCallback aCallback,
-                                  NetworkResultOptions& aResult)
+void NetworkUtils::setConfig(CommandChain* aChain,
+                             CommandCallback aCallback,
+                             NetworkResultOptions& aResult)
 {
   char command[MAX_COMMAND_SIZE];
   if (SDK_VERSION >= 16) {
     snprintf(command, MAX_COMMAND_SIZE - 1, "interface setcfg %s %s %s %s",
                      GET_CHAR(mIfname),
                      GET_CHAR(mIp),
                      GET_CHAR(mPrefix),
                      GET_CHAR(mLink));
@@ -918,16 +942,36 @@ void NetworkUtils::setInterfaceDns(Comma
     }
 
     written += ret;
   }
 
   doCommand(command, aChain, aCallback);
 }
 
+void NetworkUtils::getInterfaceList(CommandChain* aChain,
+                                    CommandCallback aCallback,
+                                    NetworkResultOptions& aResult)
+{
+  char command[MAX_COMMAND_SIZE];
+  snprintf(command, MAX_COMMAND_SIZE - 1, "interface list");
+
+  doCommand(command, aChain, aCallback);
+}
+
+void NetworkUtils::getConfig(CommandChain* aChain,
+                             CommandCallback aCallback,
+                             NetworkResultOptions& aResult)
+{
+  char command[MAX_COMMAND_SIZE];
+  snprintf(command, MAX_COMMAND_SIZE - 1, "interface getcfg %s", GET_CHAR(mIfname));
+
+  doCommand(command, aChain, aCallback);
+}
+
 #undef GET_CHAR
 #undef GET_FIELD
 
 /*
  * Netd command success/fail function
  */
 #define ASSIGN_FIELD(prop)  aResult.prop = aChain->getParams().prop;
 #define ASSIGN_FIELD_VALUE(prop, value)  aResult.prop = value;
@@ -1058,16 +1102,85 @@ void NetworkUtils::wifiOperationModeSucc
   postMessage(aChain->getParams(), aResult);
 }
 
 void NetworkUtils::setDnsFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
 {
   postMessage(aOptions, aResult);
 }
 
+void NetworkUtils::getInterfacesFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+  postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::getInterfacesSuccess(CommandChain* aChain,
+                                        CommandCallback aCallback,
+                                        NetworkResultOptions& aResult)
+{
+  char buf[BUF_SIZE];
+  NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+  memcpy(buf, reason.get(), strlen(reason.get()));
+
+  nsTArray<nsCString> result;
+  split(buf, INTERFACE_DELIMIT, result);
+
+  nsTArray<nsString> interfaceList;
+  uint32_t length = result.Length();
+  convertUTF8toUTF16(result, interfaceList, length);
+
+  aResult.mInterfaceList.Construct();
+  for (uint32_t i = 0; i < length; i++) {
+    aResult.mInterfaceList.Value().AppendElement(interfaceList[i]);
+  }
+
+  postMessage(aChain->getParams(), aResult);
+}
+
+void NetworkUtils::setInterfaceConfigFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+  postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::setInterfaceConfigSuccess(CommandChain* aChain,
+                                             CommandCallback aCallback,
+                                             NetworkResultOptions& aResult)
+{
+  postMessage(aChain->getParams(), aResult);
+}
+
+void NetworkUtils::getInterfaceConfigFail(NetworkParams& aOptions, NetworkResultOptions& aResult)
+{
+  postMessage(aOptions, aResult);
+}
+
+void NetworkUtils::getInterfaceConfigSuccess(CommandChain* aChain,
+                                             CommandCallback aCallback,
+                                             NetworkResultOptions& aResult)
+{
+  char buf[BUF_SIZE];
+  NS_ConvertUTF16toUTF8 reason(aResult.mResultReason);
+  memcpy(buf, reason.get(), strlen(reason.get()));
+
+  nsTArray<nsCString> result;
+  split(buf, NETD_MESSAGE_DELIMIT, result);
+
+  ASSIGN_FIELD_VALUE(mMacAddr, NS_ConvertUTF8toUTF16(result[0]))
+  ASSIGN_FIELD_VALUE(mIpAddr, NS_ConvertUTF8toUTF16(result[1]))
+  ASSIGN_FIELD_VALUE(mMaskLength, atof(result[2].get()))
+
+  if (result[3].Find("up")) {
+    ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("up"))
+  } else {
+    ASSIGN_FIELD_VALUE(mFlag, NS_ConvertUTF8toUTF16("down"))
+  }
+
+  postMessage(aChain->getParams(), aResult);
+}
+
 #undef ASSIGN_FIELD
 #undef ASSIGN_FIELD_VALUE
 
 NetworkUtils::NetworkUtils(MessageCallback aCallback)
  : mMessageCallback(aCallback)
 {
   mNetUtils = new NetUtils();
 
@@ -1122,16 +1235,24 @@ void NetworkUtils::ExecuteCommand(Networ
   } else if (aOptions.mCmd.EqualsLiteral("setWifiTethering")) {
     setWifiTethering(aOptions);
   } else if (aOptions.mCmd.EqualsLiteral("setUSBTethering")) {
     setUSBTethering(aOptions);
   } else if (aOptions.mCmd.EqualsLiteral("enableUsbRndis")) {
     enableUsbRndis(aOptions);
   } else if (aOptions.mCmd.EqualsLiteral("updateUpStream")) {
     updateUpStream(aOptions);
+  } else if (aOptions.mCmd.EqualsLiteral("getInterfaces")) {
+    getInterfaces(aOptions);
+  } else if (aOptions.mCmd.EqualsLiteral("stopDhcp")) {
+    stopDhcp(aOptions);
+  } else if (aOptions.mCmd.EqualsLiteral("setInterfaceConfig")) {
+    setInterfaceConfig(aOptions);
+  } else if (aOptions.mCmd.EqualsLiteral("getInterfaceConfig")) {
+    getInterfaceConfig(aOptions);
   } else {
     WARN("unknon message");
     return;
   }
 
   if (!aOptions.mIsAsync) {
     NetworkResultOptions result;
     result.mRet = ret;
@@ -1711,24 +1832,60 @@ bool NetworkUtils::enableUsbRndis(Networ
   if (report) {
     usleep(USB_FUNCTION_RETRY_INTERVAL * 1000);
     checkUsbRndisState(aOptions);
   }
   return true;
 }
 
 /**
- * handling upstream interface change event.
+ * Handling upstream interface change event.
  */
 bool NetworkUtils::updateUpStream(NetworkParams& aOptions)
 {
   RUN_CHAIN(aOptions, sUpdateUpStreamChain, updateUpStreamFail)
   return true;
 }
 
+/**
+ * Stop dhcp client deamon.
+ */
+bool NetworkUtils::stopDhcp(NetworkParams& aOptions)
+{
+  mNetUtils->do_dhcp_stop(GET_CHAR(mIfname));
+  return true;
+}
+
+/**
+ * Get existing network interfaces.
+ */
+bool NetworkUtils::getInterfaces(NetworkParams& aOptions)
+{
+  RUN_CHAIN(aOptions, sGetInterfacesChain, getInterfacesFail)
+  return true;
+}
+
+/**
+ * Set network config for a specified interface.
+ */
+bool NetworkUtils::setInterfaceConfig(NetworkParams& aOptions)
+{
+  RUN_CHAIN(aOptions, sSetInterfaceConfigChain, setInterfaceConfigFail)
+  return true;
+}
+
+/**
+ * Get network config of a specified interface.
+ */
+bool NetworkUtils::getInterfaceConfig(NetworkParams& aOptions)
+{
+  RUN_CHAIN(aOptions, sGetInterfaceConfigChain, getInterfaceConfigFail)
+  return true;
+}
+
 void NetworkUtils::sendBroadcastMessage(uint32_t code, char* reason)
 {
   NetworkResultOptions result;
   switch(code) {
     case NETD_COMMAND_INTERFACE_CHANGE:
       result.mTopic = NS_ConvertUTF8toUTF16("netd-interface-change");
       break;
     case NETD_COMMAND_BANDWIDTH_CONTROLLER:
--- a/dom/system/gonk/NetworkUtils.h
+++ b/dom/system/gonk/NetworkUtils.h
@@ -67,16 +67,17 @@ public:
     mEndIp = aOther.mEndIp;
     mServerIp = aOther.mServerIp;
     mMaskLength = aOther.mMaskLength;
     mPreInternalIfname = aOther.mPreInternalIfname;
     mPreExternalIfname = aOther.mPreExternalIfname;
     mCurInternalIfname = aOther.mCurInternalIfname;
     mCurExternalIfname = aOther.mCurExternalIfname;
     mThreshold = aOther.mThreshold;
+    mIsBlocking = aOther.mIsBlocking;
   }
 
   NetworkParams(const mozilla::dom::NetworkCommandOptions& aOther) {
 
 #define COPY_SEQUENCE_FIELD(prop, type)                                                      \
     if (aOther.prop.WasPassed()) {                                                           \
       mozilla::dom::Sequence<type > const & currentValue = aOther.prop.InternalValue();      \
       uint32_t length = currentValue.Length();                                               \
@@ -143,16 +144,17 @@ public:
     COPY_OPT_STRING_FIELD(mEndIp, EmptyString())
     COPY_OPT_STRING_FIELD(mServerIp, EmptyString())
     COPY_OPT_STRING_FIELD(mMaskLength, EmptyString())
     COPY_OPT_STRING_FIELD(mPreInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mPreExternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mCurInternalIfname, EmptyString())
     COPY_OPT_STRING_FIELD(mCurExternalIfname, EmptyString())
     COPY_OPT_FIELD(mThreshold, -1)
+    COPY_OPT_FIELD(mIsBlocking, false)
 
 #undef COPY_SEQUENCE_FIELD
 #undef COPY_OPT_STRING_FIELD
 #undef COPY_OPT_FIELD
 #undef COPY_FIELD
   }
 
   int32_t mId;
@@ -193,16 +195,17 @@ public:
   nsString mEndIp;
   nsString mServerIp;
   nsString mMaskLength;
   nsString mPreInternalIfname;
   nsString mPreExternalIfname;
   nsString mCurInternalIfname;
   nsString mCurExternalIfname;
   long mThreshold;
+  bool mIsBlocking;
 };
 
 // CommandChain store the necessary information to execute command one by one.
 // Including :
 // 1. Command parameters.
 // 2. Command list.
 // 3. Error callback function.
 // 4. Index of current execution command.
@@ -276,16 +279,20 @@ private:
   bool enableNetworkInterfaceAlarm(NetworkParams& aOptions);
   bool disableNetworkInterfaceAlarm(NetworkParams& aOptions);
   bool setWifiOperationMode(NetworkParams& aOptions);
   bool setDhcpServer(NetworkParams& aOptions);
   bool setWifiTethering(NetworkParams& aOptions);
   bool setUSBTethering(NetworkParams& aOptions);
   bool enableUsbRndis(NetworkParams& aOptions);
   bool updateUpStream(NetworkParams& aOptions);
+  bool getInterfaces(NetworkParams& aOptions);
+  bool stopDhcp(NetworkParams& aOptions);
+  bool setInterfaceConfig(NetworkParams& aOptions);
+  bool getInterfaceConfig(NetworkParams& aOptions);
 
   /**
    * function pointer array holds all netd commands should be executed
    * in sequence to accomplish a given command by other module.
    */
   static CommandFunc sWifiEnableChain[];
   static CommandFunc sWifiDisableChain[];
   static CommandFunc sWifiFailChain[];
@@ -297,16 +304,19 @@ private:
   static CommandFunc sUpdateUpStreamChain[];
   static CommandFunc sStartDhcpServerChain[];
   static CommandFunc sStopDhcpServerChain[];
   static CommandFunc sNetworkInterfaceStatsChain[];
   static CommandFunc sNetworkInterfaceEnableAlarmChain[];
   static CommandFunc sNetworkInterfaceDisableAlarmChain[];
   static CommandFunc sNetworkInterfaceSetAlarmChain[];
   static CommandFunc sSetDnsChain[];
+  static CommandFunc sGetInterfacesChain[];
+  static CommandFunc sSetInterfaceConfigChain[];
+  static CommandFunc sGetInterfaceConfigChain[];
 
   /**
    * Individual netd command stored in command chain.
    */
 #define PARAMS CommandChain* aChain, CommandCallback aCallback, \
                mozilla::dom::NetworkResultOptions& aResult
   static void wifiFirmwareReload(PARAMS);
   static void startAccessPointDriver(PARAMS);
@@ -333,38 +343,47 @@ private:
   static void stopTethering(PARAMS);
   static void startTethering(PARAMS);
   static void untetherInterface(PARAMS);
   static void setDnsForwarders(PARAMS);
   static void enableNat(PARAMS);
   static void disableNat(PARAMS);
   static void setDefaultInterface(PARAMS);
   static void setInterfaceDns(PARAMS);
+  static void getInterfaceList(PARAMS);
+  static void setConfig(PARAMS);
+  static void getConfig(PARAMS);
   static void wifiTetheringSuccess(PARAMS);
   static void usbTetheringSuccess(PARAMS);
   static void networkInterfaceStatsSuccess(PARAMS);
   static void networkInterfaceAlarmSuccess(PARAMS);
   static void updateUpStreamSuccess(PARAMS);
   static void setDhcpServerSuccess(PARAMS);
   static void wifiOperationModeSuccess(PARAMS);
+  static void getInterfacesSuccess(PARAMS);
+  static void setInterfaceConfigSuccess(PARAMS);
+  static void getInterfaceConfigSuccess(PARAMS);
 #undef PARAMS
 
   /**
    * Error callback function executed when a command is fail.
    */
 #define PARAMS NetworkParams& aOptions, \
                mozilla::dom::NetworkResultOptions& aResult
   static void wifiTetheringFail(PARAMS);
   static void wifiOperationModeFail(PARAMS);
   static void usbTetheringFail(PARAMS);
   static void updateUpStreamFail(PARAMS);
   static void setDhcpServerFail(PARAMS);
   static void networkInterfaceStatsFail(PARAMS);
   static void networkInterfaceAlarmFail(PARAMS);
   static void setDnsFail(PARAMS);
+  static void getInterfacesFail(PARAMS);
+  static void setInterfaceConfigFail(PARAMS);
+  static void getInterfaceConfigFail(PARAMS);
 #undef PARAMS
 
   /**
    * Command chain processing functions.
    */
   static void next(CommandChain* aChain, bool aError,
                    mozilla::dom::NetworkResultOptions& aResult);
   static void nextNetdCommand();
--- a/dom/system/gonk/NetworkWorker.cpp
+++ b/dom/system/gonk/NetworkWorker.cpp
@@ -13,16 +13,18 @@
 
 #define NS_NETWORKWORKER_CID \
   { 0x6df093e1, 0x8127, 0x4fa7, {0x90, 0x13, 0xa3, 0xaa, 0xa7, 0x79, 0xbb, 0xdd} }
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
+#define PROPERTY_VALUE_MAX 80
+
 namespace mozilla {
 
 nsCOMPtr<nsIThread> gWorkerThread;
 
 // The singleton network worker, to be used on the main thread.
 StaticRefPtr<NetworkWorker> gNetworkWorker;
 
 // The singleton networkutils class, that can be used on any thread.
@@ -32,32 +34,51 @@ static nsAutoPtr<NetworkUtils> gNetworkU
 class NetworkResultDispatcher : public nsRunnable
 {
 public:
   NetworkResultDispatcher(const NetworkResultOptions& aResult)
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
 #define COPY_FIELD(prop) mResult.prop = aResult.prop;
+#define COPY_SEQUENCE_FIELD(prop, type)                                                   \
+    if (aResult.prop.WasPassed()) {                                                       \
+      mozilla::dom::Sequence<type > const & currentValue = aResult.prop.InternalValue();  \
+      uint32_t length = currentValue.Length();                                            \
+      mResult.prop.Construct();                                                           \
+      for (uint32_t idx = 0; idx < length; idx++) {                                       \
+        mResult.prop.Value().AppendElement(currentValue[idx]);                            \
+      }                                                                                   \
+    }
     COPY_FIELD(mId)
     COPY_FIELD(mRet)
     COPY_FIELD(mBroadcast)
     COPY_FIELD(mTopic)
     COPY_FIELD(mReason)
     COPY_FIELD(mResultCode)
     COPY_FIELD(mResultReason)
     COPY_FIELD(mError)
     COPY_FIELD(mRxBytes)
     COPY_FIELD(mTxBytes)
     COPY_FIELD(mDate)
     COPY_FIELD(mEnable)
     COPY_FIELD(mResult)
     COPY_FIELD(mSuccess)
     COPY_FIELD(mCurExternalIfname)
     COPY_FIELD(mCurInternalIfname)
+    COPY_FIELD(mIpAddr)
+    COPY_FIELD(mGateway)
+    COPY_FIELD(mDns1)
+    COPY_FIELD(mDns2)
+    COPY_FIELD(mServer)
+    COPY_FIELD(mLease)
+    COPY_FIELD(mVendorInfo)
+    COPY_FIELD(mMaskLength)
+    COPY_FIELD(mFlag)
+    COPY_SEQUENCE_FIELD(mInterfaceList, nsString)
 #undef COPY_FIELD
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (gNetworkWorker) {
@@ -87,16 +108,76 @@ public:
       gNetworkUtils->ExecuteCommand(mParams);
     }
     return NS_OK;
   }
 private:
   NetworkParams mParams;
 };
 
+// Runnable used for blocking command.
+class RunDhcpEvent : public nsRunnable
+{
+public:
+  RunDhcpEvent(const NetworkParams& aParams)
+  : mParams(aParams)
+  {}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(!NS_IsMainThread());
+
+    nsAutoPtr<NetUtils> netUtils;
+    netUtils = new NetUtils();
+
+    NetworkResultOptions result;
+    result.mId = mParams.mId;
+
+    int32_t status;
+    char ipaddr[PROPERTY_VALUE_MAX];
+    char gateway[PROPERTY_VALUE_MAX];
+    uint32_t prefixLength;
+    char dns1[PROPERTY_VALUE_MAX];
+    char dns2[PROPERTY_VALUE_MAX];
+    char server[PROPERTY_VALUE_MAX];
+    uint32_t lease;
+    char vendorinfo[PROPERTY_VALUE_MAX];
+    status = netUtils->do_dhcp_do_request(NS_ConvertUTF16toUTF8(mParams.mIfname).get(),
+                                          ipaddr,
+                                          gateway,
+                                          &prefixLength,
+                                          dns1,
+                                          dns2,
+                                          server,
+                                          &lease,
+                                          vendorinfo);
+
+
+    if (status == 0) {
+      // run dhcp success.
+      result.mSuccess = true;
+      result.mIpAddr = NS_ConvertUTF8toUTF16(ipaddr);
+      result.mGateway = NS_ConvertUTF8toUTF16(gateway);
+      result.mDns1 = NS_ConvertUTF8toUTF16(dns1);
+      result.mDns2 = NS_ConvertUTF8toUTF16(dns2);
+      result.mServer = NS_ConvertUTF8toUTF16(server);
+      result.mLease = lease;
+      result.mVendorInfo = NS_ConvertUTF8toUTF16(vendorinfo);
+      result.mMaskLength = prefixLength;
+    }
+
+    nsCOMPtr<nsIRunnable> runnable = new NetworkResultDispatcher(result);
+    NS_DispatchToMainThread(runnable);
+
+    return NS_OK;
+  }
+private:
+  NetworkParams mParams;
+};
+
 // Runnable used dispatch netd result on the worker thread.
 class NetdEventRunnable : public nsRunnable
 {
 public:
   NetdEventRunnable(NetdCommand* aCommand)
     : mCommand(aCommand)
   {
     MOZ_ASSERT(!NS_IsMainThread());
@@ -219,26 +300,50 @@ NetworkWorker::PostMessage(JS::Handle<JS
   MOZ_ASSERT(NS_IsMainThread());
 
   NetworkCommandOptions options;
   if (!options.Init(aCx, aOptions)) {
     NS_WARNING("Bad dictionary passed to NetworkWorker::SendCommand");
     return NS_ERROR_FAILURE;
   }
 
+  NetworkParams NetworkParams(options);
+
+  if (NetworkParams.mIsBlocking) {
+    NetworkWorker::HandleBlockingCommand(NetworkParams);
+    return NS_OK;
+  }
+
   // Dispatch the command to the control thread.
-  NetworkParams NetworkParams(options);
   nsCOMPtr<nsIRunnable> runnable = new NetworkCommandDispatcher(NetworkParams);
   if (gWorkerThread) {
     gWorkerThread->Dispatch(runnable, nsIEventTarget::DISPATCH_NORMAL);
   }
   return NS_OK;
 }
 
 void
+NetworkWorker::HandleBlockingCommand(NetworkParams& aOptions)
+{
+  if (aOptions.mCmd.EqualsLiteral("runDhcp")) {
+    NetworkWorker::RunDhcp(aOptions);
+  }
+}
+
+void
+NetworkWorker::RunDhcp(NetworkParams& aOptions)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIRunnable> runnable = new RunDhcpEvent(aOptions);
+  nsCOMPtr<nsIThread> thread;
+  NS_NewThread(getter_AddRefs(thread), runnable);
+}
+
+void
 NetworkWorker::DispatchNetworkResult(const NetworkResultOptions& aOptions)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mozilla::AutoSafeJSContext cx;
   JS::RootedValue val(cx);
 
   if (!ToJSValue(cx, aOptions, &val)) {
--- a/dom/system/gonk/NetworkWorker.h
+++ b/dom/system/gonk/NetworkWorker.h
@@ -6,16 +6,18 @@
 #define NetworkWorker_h
 
 #include "mozilla/dom/NetworkOptionsBinding.h"
 #include "mozilla/ipc/Netd.h"
 #include "nsINetworkWorker.h"
 #include "nsCOMPtr.h"
 #include "nsThread.h"
 
+class NetworkParams;
+
 namespace mozilla {
 
 class NetworkWorker MOZ_FINAL : public nsINetworkWorker
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSINETWORKWORKER
 
@@ -24,14 +26,17 @@ public:
   void DispatchNetworkResult(const mozilla::dom::NetworkResultOptions& aOptions);
 
 private:
   NetworkWorker();
   ~NetworkWorker();
 
   static void NotifyResult(mozilla::dom::NetworkResultOptions& aResult);
 
+  void HandleBlockingCommand(NetworkParams& aParams);
+  void RunDhcp(NetworkParams& aParams);
+
   nsCOMPtr<nsINetworkEventListener> mListener;
 };
 
 } // namespace mozilla
 
 #endif  // NetworkWorker_h
--- a/dom/system/gonk/nsINetworkManager.idl
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -4,24 +4,26 @@
 
 #include "nsISupports.idl"
 
 interface nsIWifiTetheringCallback;
 
 /**
  * Information about networks that is exposed to network manager API consumers.
  */
-[scriptable, uuid(cb62ae03-6bda-43ff-9560-916d60203d33)]
+[scriptable, uuid(8f9ab9e0-72c1-4874-80c7-8143353b979f)]
 interface nsINetworkInterface : nsISupports
 {
   const long NETWORK_STATE_UNKNOWN = -1;
   const long NETWORK_STATE_CONNECTING = 0;
   const long NETWORK_STATE_CONNECTED = 1;
   const long NETWORK_STATE_DISCONNECTING = 2;
   const long NETWORK_STATE_DISCONNECTED = 3;
+  const long NETWORK_STATE_ENABLED = 4;
+  const long NETWORK_STATE_DISABLED = 5;
 
   /**
    * Current network state, one of the NETWORK_STATE_* constants.
    *
    * When this changes, network interface implementations notify the
    * 'network-interface-state-changed' observer notification.
    */
   readonly attribute long state;
@@ -29,16 +31,17 @@ interface nsINetworkInterface : nsISuppo
   const long NETWORK_TYPE_UNKNOWN     = -1;
   const long NETWORK_TYPE_WIFI        = 0;
   const long NETWORK_TYPE_MOBILE      = 1;
   const long NETWORK_TYPE_MOBILE_MMS  = 2;
   const long NETWORK_TYPE_MOBILE_SUPL = 3;
   const long NETWORK_TYPE_WIFI_P2P    = 4;
   const long NETWORK_TYPE_MOBILE_IMS  = 5;
   const long NETWORK_TYPE_MOBILE_DUN  = 6;
+  const long NETWORK_TYPE_ETHERNET    = 7;
 
   /**
    * Network type. One of the NETWORK_TYPE_* constants.
    */
   readonly attribute long type;
 
   /**
    * Name of the network interface. This identifier is unique.
@@ -92,17 +95,17 @@ interface nsINetworkInterface : nsISuppo
    */
   void getDnses([optional] out unsigned long count,
                 [array, size_is(count), retval] out wstring dnses);
 };
 
 /**
  * Manage network interfaces.
  */
-[scriptable, uuid(3ea50550-4b3c-11e3-8f96-0800200c9a66)]
+[scriptable, uuid(1ba9346b-53b5-4660-9dc6-58f0b258d0a6)]
 interface nsINetworkManager : nsISupports
 {
   /**
    * Register the given network interface with the network manager.
    *
    * Consumers will be notified with the 'network-interface-registered'
    * observer notification.
    *
@@ -131,16 +134,25 @@ interface nsINetworkManager : nsISupport
    * Object containing all known network connections, keyed by their
    * network id. Network id is composed of a sub-id + '-' + network
    * type. For mobile network types, sub-id is 'ril' + service id; for
    * non-mobile network types, sub-id is always 'device'.
    */
   readonly attribute jsval networkInterfaces;
 
   /**
+   * Priority list of network types. An array of
+   * nsINetworkInterface::NETWORK_TYPE_* constants.
+   *
+   * The piror position of the type indicates the higher priority. The priority
+   * is used to determine route when there are multiple connected networks.
+   */
+  attribute jsval networkTypePriorityList;
+
+  /**
    * The preferred network type. One of the
    * nsINetworkInterface::NETWORK_TYPE_* constants.
    *
    * This attribute is used for setting default route to favor
    * interfaces with given type.  This can be overriden by calling
    * overrideDefaultRoute().
    */
   attribute long preferredNetworkType;
--- a/dom/system/gonk/nsINetworkService.idl
+++ b/dom/system/gonk/nsINetworkService.idl
@@ -96,20 +96,75 @@ interface nsIUpdateUpStreamCallback : ns
    * @param success
    *        Boolean to indicate the operation is successful or not.
    * @param externalIfname
    *        The external interface name.
    */
   void updateUpStreamResult(in boolean success, in DOMString externalIfname);
 };
 
+[scriptable, function, uuid(4a9166f3-7e4f-4d10-bb5c-b49ee21d6184)]
+interface nsIRunDhcpCallback : nsISupports
+{
+  /**
+   * Callback function used to report the result of dhcp request.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   */
+  void runDhcpResult(in boolean success, in jsval result);
+};
+
+[scriptable, function, uuid(88e3ee22-f1b3-4fa0-8a5d-793fb827c42c)]
+interface nsIGetInterfacesCallback : nsISupports
+{
+  /**
+   * Callback function used to return the list of existing network interfaces.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   * @param interfaceList
+   *        An array of interface name.
+   */
+  void getInterfacesResult(in boolean success, in jsval interfaceList);
+};
+
+[scriptable, function, uuid(064e02a3-d2c0-42c5-a293-1efa84056100)]
+interface nsIGetInterfaceConfigCallback : nsISupports
+{
+  /**
+   * Callback function used to return the network config of a given interface.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   * @param result
+   *        .ip: Ip address.
+   *        .prefix: mask length.
+   *        .link: network link properties.
+   *        .mac: mac address.
+   */
+  void getInterfaceConfigResult(in boolean success, in jsval result);
+};
+
+[scriptable, function, uuid(b370f360-6ba8-4517-a4f9-31e8f004ee91)]
+interface nsISetInterfaceConfigCallback : nsISupports
+{
+  /**
+   * Callback function used to set network cofig for a specified interface.
+   *
+   * @param success
+   *        Boolean to indicate the operation is successful or not.
+   */
+  void setInterfaceConfigResult(in boolean success);
+};
+
 /**
  * Provide network services.
  */
-[scriptable, uuid(f96461fa-e844-45d2-a6c3-8cd23ab0916b)]
+[scriptable, uuid(48c13741-aec9-4a86-8962-432011708261)]
 interface nsINetworkService : nsISupports
 {
   /**
    * Enable or disable Wifi Tethering
    *
    * @param enabled
    *        Boolean that indicates whether tethering should be enabled (true) or disabled (false).
    * @param config
@@ -340,9 +395,58 @@ interface nsINetworkService : nsISupport
    * @param current
    *        The current internal and external interface.
    * @param callback
    *        Callback function to report the result.
    */
   void updateUpStream(in jsval previous,
                       in jsval current,
                       in nsIUpdateUpStreamCallback callback);
+
+  /**
+   * Run Dhcp request.
+   *
+   * @param ifname
+   *        Target interface.
+   * @param callback
+   *        Callback function to report the result.
+   */
+  void runDhcp(in DOMString ifname, in nsIRunDhcpCallback callback);
+
+  /**
+   * Stop Dhcp daemon.
+   *
+   * @param ifname
+   *        Target interface.
+   */
+  void stopDhcp(in DOMString ifname);
+
+  /*
+   * Obtain interfaces list.
+   *
+   * @param callback
+   *        Callback function to return the result.
+   */
+  void getInterfaces(in nsIGetInterfacesCallback callback);
+
+  /**
+   * Set config for a network interface.
+   *
+   * @param config
+   *        .ifname: Target interface.
+   *        .ip: Ip address.
+   *        .prefix: mask length.
+   *        .link: network link properties.
+   * @param callback
+   *        Callback function to report the result.
+   */
+  void setInterfaceConfig(in jsval config, in nsISetInterfaceConfigCallback callback);
+
+  /**
+   * Get config of a network interface.
+   *
+   * @param ifname
+   *        Target interface.
+   * @param callback
+   *        Callback function to report the result.
+   */
+  void getInterfaceConfig(in DOMString ifname, in nsIGetInterfaceConfigCallback callback);
 };
--- a/dom/voicemail/Voicemail.cpp
+++ b/dom/voicemail/Voicemail.cpp
@@ -15,17 +15,17 @@
 #include "nsDOMClassInfo.h"
 #include "nsServiceManagerUtils.h"
 
 #define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
 const char* kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
 
 using namespace mozilla::dom;
 
-class Voicemail::Listener : public nsIVoicemailListener
+class Voicemail::Listener MOZ_FINAL : public nsIVoicemailListener
 {
   Voicemail* mVoicemail;
 
 public:
   NS_DECL_ISUPPORTS
   NS_FORWARD_SAFE_NSIVOICEMAILLISTENER(mVoicemail)
 
   Listener(Voicemail* aVoicemail)
--- a/dom/webidl/NetworkOptions.webidl
+++ b/dom/webidl/NetworkOptions.webidl
@@ -1,14 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /**
-* This dictionnary holds the parameters sent to the network worker.
+* This dictionary holds the parameters sent to the network worker.
 */
 dictionary NetworkCommandOptions
 {
   long id = 0;                        // opaque id.
   DOMString cmd = "";                 // the command name.
   DOMString ifname;                   // for "removeNetworkRoute", "setDNS",
                                       //     "setDefaultRouteAndDNS", "removeDefaultRoute"
                                       //     "addHostRoute", "removeHostRoute"
@@ -50,16 +50,17 @@ dictionary NetworkCommandOptions
   DOMString startIp;                  // for "setDhcpServer".
   DOMString endIp;                    // for "setDhcpServer".
   DOMString serverIp;                 // for "setDhcpServer".
   DOMString maskLength;               // for "setDhcpServer".
   DOMString preInternalIfname;        // for "updateUpStream".
   DOMString preExternalIfname;        // for "updateUpStream".
   DOMString curInternalIfname;        // for "updateUpStream".
   DOMString curExternalIfname;        // for "updateUpStream".
+  boolean isBlocking;                 // for "runDhcp".
 };
 
 /**
 * This dictionary holds the parameters sent back to NetworkService.js.
 */
 dictionary NetworkResultOptions
 {
   long id = 0;                        // opaque id.
@@ -76,9 +77,23 @@ dictionary NetworkResultOptions
   float txBytes = -1;                 // for "getNetworkInterfaceStats".
   DOMString date = "";                // for "getNetworkInterfaceStats".
   boolean enable = false;             // for "setWifiTethering", "setUSBTethering"
                                       //     "enableUsbRndis".
   boolean result = false;             // for "enableUsbRndis".
   boolean success = false;            // for "setDhcpServer".
   DOMString curExternalIfname = "";   // for "updateUpStream".
   DOMString curInternalIfname = "";   // for "updateUpStream".
+
+  DOMString ipAddr = "";              // for "runDhcp", "getInterfaceConfig".
+  DOMString gateway = "";             // for "runDhcp".
+  DOMString dns1 = "";                // for "runDhcp".
+  DOMString dns2 = "";                // for "runDhcp".
+  DOMString server = "";              // for "runDhcp".
+  short lease = 0;                    // for "runDhcp".
+  DOMString vendorInfo = "";          // for "runDhcp".
+  short maskLength = 0;               // for "runDhcp".
+
+  DOMString flag = "down";            // for "getInterfaceConfig".
+  DOMString macAddr = "";             // for "getInterfaceConfig".
+
+  sequence<DOMString> interfaceList;  // for "getInterfaceList".
 };
--- a/testing/marionette/client/marionette/tests/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit-tests.ini
@@ -23,14 +23,15 @@ skip = false
 [include:../../../../../dom/mobileconnection/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/gonk/tests/marionette/manifest.ini]
 [include:../../../../../dom/icc/tests/marionette/manifest.ini]
 [include:../../../../../dom/system/tests/marionette/manifest.ini]
 [include:../../../../../dom/nfc/tests/marionette/manifest.ini]
 [include:../../../../../dom/events/test/marionette/manifest.ini]
 [include:../../../../../dom/wifi/test/marionette/manifest.ini]
 [include:../../../../../dom/cellbroadcast/tests/marionette/manifest.ini]
+[include:../../../../../dom/network/tests/marionette/manifest.ini]
 
 ; layout tests
 [include:../../../../../layout/base/tests/marionette/manifest.ini]
 
 ; loop tests
 [include:../../../../../browser/components/loop/manifest.ini]
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -257,17 +257,17 @@ WebappsActor.prototype = {
         aApp.manifest = manifest;
 
         // Needed to evict manifest cache on content side
         // (has to be dispatched first, otherwise other messages like
         // Install:Return:OK are going to use old manifest version)
         reg.broadcastMessage("Webapps:UpdateState", {
           app: aApp,
           manifest: manifest,
-          manifestURL: aApp.manifestURL
+          id: aApp.id
         });
         reg.broadcastMessage("Webapps:FireEvent", {
           eventType: ["downloadsuccess", "downloadapplied"],
           manifestURL: aApp.manifestURL
         });
         reg.broadcastMessage("Webapps:AddApp", { id: aId, app: aApp });
         reg.broadcastMessage("Webapps:Install:Return:OK", {
           app: aApp,