diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
index faa86541ceb044a1a51fa24930c12cbfd8d510c8..5e9d3924c1e7abe2d4150315c2f3d1287cba69f3 100644
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -307,12 +307,10 @@ pref("image.onload.decode.limit", 24); /* don't decode more than 24 images eager
 
 // XXX this isn't a good check for "are touch events supported", but
 // we don't really have a better one at the moment.
-#ifdef MOZ_WIDGET_GONK
 // enable touch events interfaces
 pref("dom.w3c_touch_events.enabled", 1);
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
-#endif
 
 #ifdef MOZ_SAFE_BROWSING
 // Safe browsing does nothing unless this pref is set
diff --git a/b2g/chrome/content/desktop.js b/b2g/chrome/content/desktop.js
new file mode 100644
index 0000000000000000000000000000000000000000..311d2e886c8b77badf0b389acd1375f4d443bd9c
--- /dev/null
+++ b/b2g/chrome/content/desktop.js
@@ -0,0 +1,10 @@
+
+window.addEventListener("ContentStart", function(evt) {
+  // Enable touch event shim on desktop that translates mouse events
+  // into touch ones
+  let require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {})
+                  .devtools.require;
+  let { TouchEventHandler } = require("devtools/touch-events");
+  let touchEventHandler = new TouchEventHandler(shell.contentBrowser);
+  touchEventHandler.start();
+});
diff --git a/b2g/chrome/content/shell.html b/b2g/chrome/content/shell.html
index 8bab69a3bd7bb6c98b773261adff8dad9fad76c1..0cdae8285d9f61df5a7d7ab66c3c41df890c8551 100644
--- a/b2g/chrome/content/shell.html
+++ b/b2g/chrome/content/shell.html
@@ -19,7 +19,9 @@
           src="chrome://browser/content/shell.js"> </script>
 
 #ifndef MOZ_WIDGET_GONK
-
+  <!-- various task that has to happen only on desktop -->
+  <script type="application/javascript;version=1.8"
+          src="chrome://browser/content/desktop.js"> </script>
   <!-- this script handles the screen argument for desktop builds -->
   <script type="application/javascript;version=1.8"
           src="chrome://browser/content/screen.js"> </script>
diff --git a/b2g/chrome/jar.mn b/b2g/chrome/jar.mn
index 632668d3f085efabb422df67de66f0c21337e918..4da1121275e33c4d14d00676e81731cb2887062c 100644
--- a/b2g/chrome/jar.mn
+++ b/b2g/chrome/jar.mn
@@ -14,6 +14,7 @@ chrome.jar:
 * content/shell.html                    (content/shell.html)
 * content/shell.js                      (content/shell.js)
 #ifndef ANDROID
+  content/desktop.js                    (content/desktop.js)
   content/screen.js                     (content/screen.js)
   content/runapp.js                     (content/runapp.js)
 #endif
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index 73b4dca10f211b6b6d1a423e17ceee2d99d43b25..bd36f1f9db7e12eac8bd37f2975026178269fc58 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "25a3b96e9c5ff89b69b29007462bfd056ad5bf53", 
+    "revision": "ae5c954ca1b8047cfa932f905d6498b47bb44ac5", 
     "repo_path": "/integration/gaia-central"
 }
diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in
index 23243a990137740f76f61a6937c825da0954b9c9..f0ea2d6473d3326ffe588c6252f96bfee2a00a79 100644
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -402,6 +402,8 @@
 #ifdef MOZ_WIDGET_GONK
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
+@BINPATH@/components/DOMWifiP2pManager.js
+@BINPATH@/components/DOMWifiP2pManager.manifest
 @BINPATH@/components/NetworkInterfaceListService.js
 @BINPATH@/components/NetworkInterfaceListService.manifest
 @BINPATH@/components/NetworkManager.js
diff --git a/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js b/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
index 9c461d1aabb807f7c0502c65a6cdfca02750a2c3..4e990d048e37a0ac5753934657498e2282fa43a0 100644
--- a/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveui_touch.js
@@ -25,11 +25,11 @@ function test() {
   function testWithNoTouch() {
     let div = content.document.querySelector("div");
     let x = 2, y = 2;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
     x += 20; y += 10;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "", "touch didn't work");
-    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     testWithTouch();
   }
 
@@ -37,11 +37,11 @@ function test() {
     gBrowser.selectedTab.__responsiveUI.enableTouch();
     let div = content.document.querySelector("div");
     let x = 2, y = 2;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
     x += 20; y += 10;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "translate(20px, 10px)", "touch worked");
-    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     is(div.style.transform, "none", "end event worked");
     mgr.toggle(window, gBrowser.selectedTab);
   }
@@ -50,11 +50,11 @@ function test() {
     gBrowser.selectedTab.__responsiveUI.disableTouch();
     let div = content.document.querySelector("div");
     let x = 2, y = 2;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousedown", isSynthesized: false}, content);
     x += 20; y += 10;
-    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mousemove", isSynthesized: false}, content);
     is(div.style.transform, "", "touch didn't work");
-    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup"}, content);
+    EventUtils.synthesizeMouse(div, x, y, {type: "mouseup", isSynthesized: false}, content);
     finishUp();
   }
 
diff --git a/content/base/test/test_XHR_system.html b/content/base/test/test_XHR_system.html
index 4bdc4fe82a63caf9f7425c5d6744cc4c770be5c7..31fef6d64b222839077be4131eb94544f564569d 100644
--- a/content/base/test/test_XHR_system.html
+++ b/content/base/test/test_XHR_system.html
@@ -82,6 +82,7 @@ tests.push(function test_redirect_to_file_uri() {
 
 function runNextTest() {
   if (!tests.length) {
+    SimpleTest.finish();
     return;
   }
   tests.shift()();
@@ -89,14 +90,8 @@ function runNextTest() {
 
 function runTests() {
   SimpleTest.waitForExplicitFinish();
-  SpecialPowers.addPermission("systemXHR", true, document);
-
-  tests.push(function tearDown() {
-    SpecialPowers.removePermission("systemXHR", document);
-    SimpleTest.finish();
-  });
 
-  runNextTest();
+  SpecialPowers.pushPermissions([{'type': 'systemXHR', 'allow': true, 'context': document}], runNextTest);
 }
 
 </script>
diff --git a/content/media/encoder/MediaEncoder.cpp b/content/media/encoder/MediaEncoder.cpp
index a04c992cfde6bf44adbb07bed1c82d83743d6534..ea50fc20413cea038a47f53798285f34d1db49a6 100644
--- a/content/media/encoder/MediaEncoder.cpp
+++ b/content/media/encoder/MediaEncoder.cpp
@@ -7,6 +7,7 @@
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "prlog.h"
+#include "mozilla/Preferences.h"
 
 #ifdef MOZ_OGG
 #include "OggWriter.h"
@@ -92,7 +93,7 @@ MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
     return nullptr;
   }
 #ifdef MOZ_WEBM_ENCODER
-  else if (MediaDecoder::IsWebMEnabled() &&
+  else if (MediaEncoder::IsWebMEncoderEnabled() &&
           (aMIMEType.EqualsLiteral(VIDEO_WEBM) ||
           (aTrackTypes & ContainerWriter::HAS_VIDEO))) {
     if (aTrackTypes & ContainerWriter::HAS_AUDIO) {
@@ -107,8 +108,9 @@ MediaEncoder::CreateEncoder(const nsAString& aMIMEType, uint8_t aTrackTypes)
   }
 #endif //MOZ_WEBM_ENCODER
 #ifdef MOZ_OMX_ENCODER
-  else if (aMIMEType.EqualsLiteral(VIDEO_MP4) ||
-          (aTrackTypes & ContainerWriter::HAS_VIDEO)) {
+  else if (MediaEncoder::IsOMXEncoderEnabled() &&
+          (aMIMEType.EqualsLiteral(VIDEO_MP4) ||
+          (aTrackTypes & ContainerWriter::HAS_VIDEO))) {
     if (aTrackTypes & ContainerWriter::HAS_AUDIO) {
       audioEncoder = new OmxAudioTrackEncoder();
       NS_ENSURE_TRUE(audioEncoder, nullptr);
@@ -296,4 +298,20 @@ MediaEncoder::CopyMetadataToMuxer(TrackEncoder *aTrackEncoder)
   return rv;
 }
 
+#ifdef MOZ_WEBM_ENCODER
+bool
+MediaEncoder::IsWebMEncoderEnabled()
+{
+  return Preferences::GetBool("media.encoder.webm.enabled");
+}
+#endif
+
+#ifdef MOZ_OMX_ENCODER
+bool
+MediaEncoder::IsOMXEncoderEnabled()
+{
+  return Preferences::GetBool("media.encoder.omx.enabled");
+}
+#endif
+
 }
diff --git a/content/media/encoder/MediaEncoder.h b/content/media/encoder/MediaEncoder.h
index 933363b643eb70ce6fc869f74920d9b1e505ec41..f4e1d3087905d52ecaf0e3a44200340f340b2c46 100644
--- a/content/media/encoder/MediaEncoder.h
+++ b/content/media/encoder/MediaEncoder.h
@@ -128,6 +128,14 @@ public :
     return mState == ENCODE_ERROR;
   }
 
+#ifdef MOZ_WEBM_ENCODER
+  static bool IsWebMEncoderEnabled();
+#endif
+
+#ifdef MOZ_OMX_ENCODER
+  static bool IsOMXEncoderEnabled();
+#endif
+
 private:
   // Get encoded data from trackEncoder and write to muxer
   nsresult WriteEncodedDataToMuxer(TrackEncoder *aTrackEncoder);
diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp
index ce97ad23ae6a019c919a838cc376a78b17681518..e62a5d05eff4187360223ff44b5f78c2046a6f41 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1778,6 +1778,26 @@ Navigator::HasIccManagerSupport(JSContext* /* unused */,
   nsCOMPtr<nsPIDOMWindow> win = GetWindowFromGlobal(aGlobal);
   return win && CheckPermission(win, "mobileconnection");
 }
+
+/* static */
+bool
+Navigator::HasWifiManagerSupport(JSContext* /* unused */,
+                                 JSObject* aGlobal)
+{
+  // On XBL scope, the global object is NOT |window|. So we have
+  // to use nsContentUtils::GetObjectPrincipal to get the principal
+  // and test directly with permission manager.
+
+  nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal);
+
+  nsCOMPtr<nsIPermissionManager> permMgr =
+    do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
+  NS_ENSURE_TRUE(permMgr, false);
+
+  uint32_t permission = nsIPermissionManager::DENY_ACTION;
+  permMgr->TestPermissionFromPrincipal(principal, "wifi-manage", &permission);
+  return nsIPermissionManager::ALLOW_ACTION == permission;
+}
 #endif // MOZ_B2G_RIL
 
 #ifdef MOZ_B2G_BT
diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h
index 534d3c615be110c757a65f23a9e4a336ebc21097..aa93271451670845c36b1441bd5c340597a4768e 100644
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -264,6 +264,8 @@ public:
                                   JSObject* aGlobal);
   static bool HasIccManagerSupport(JSContext* /* unused */,
                                    JSObject* aGlobal);
+  static bool HasWifiManagerSupport(JSContext* /* unused */,
+                                    JSObject* aGlobal);
 #endif // MOZ_B2G_RIL
 #ifdef MOZ_B2G_BT
   static bool HasBluetoothSupport(JSContext* /* unused */, JSObject* aGlobal);
diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp
index 25c8b0db5d3ab50b6f6105170daa9a3e743ff23b..a8c095ec94136770b5020ffe4c6cbc2b6456b747 100644
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -604,11 +604,14 @@ nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
                                  bool aIgnoreRootScrollFrame,
                                  float aPressure,
                                  unsigned short aInputSourceArg,
+                                 bool aIsSynthesized,
+                                 uint8_t aOptionalArgCount,
                                  bool *aPreventDefault)
 {
   return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
                               aIgnoreRootScrollFrame, aPressure,
-                              aInputSourceArg, false, aPreventDefault);
+                              aInputSourceArg, false, aPreventDefault,
+                              aOptionalArgCount >= 4 ? aIsSynthesized : true);
 }
 
 NS_IMETHODIMP
@@ -620,12 +623,15 @@ nsDOMWindowUtils::SendMouseEventToWindow(const nsAString& aType,
                                          int32_t aModifiers,
                                          bool aIgnoreRootScrollFrame,
                                          float aPressure,
-                                         unsigned short aInputSourceArg)
+                                         unsigned short aInputSourceArg,
+                                         bool aIsSynthesized,
+                                         uint8_t aOptionalArgCount)
 {
   PROFILER_LABEL("nsDOMWindowUtils", "SendMouseEventToWindow");
   return SendMouseEventCommon(aType, aX, aY, aButton, aClickCount, aModifiers,
                               aIgnoreRootScrollFrame, aPressure,
-                              aInputSourceArg, true, nullptr);
+                              aInputSourceArg, true, nullptr,
+                              aOptionalArgCount >= 4 ? aIsSynthesized : true);
 }
 
 static LayoutDeviceIntPoint
@@ -668,7 +674,8 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
                                        float aPressure,
                                        unsigned short aInputSourceArg,
                                        bool aToWindow,
-                                       bool *aPreventDefault)
+                                       bool *aPreventDefault,
+                                       bool aIsSynthesized)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     return NS_ERROR_DOM_SECURITY_ERR;
@@ -715,7 +722,7 @@ nsDOMWindowUtils::SendMouseEventCommon(const nsAString& aType,
   event.inputSource = aInputSourceArg;
   event.clickCount = aClickCount;
   event.time = PR_IntervalNow();
-  event.mFlags.mIsSynthesizedForTests = true;
+  event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext)
diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h
index 512b0837ef026b1251e066422c63d6243941301d..21ddf7ee30080a20c102430a62c1ee2ba5314e57 100644
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -46,7 +46,8 @@ protected:
                                   float aPressure,
                                   unsigned short aInputSourceArg,
                                   bool aToWindow,
-                                  bool *aPreventDefault);
+                                  bool *aPreventDefault,
+                                  bool aIsSynthesized);
 
   NS_IMETHOD SendTouchEventCommon(const nsAString& aType,
                                   uint32_t* aIdentifiers,
diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini
index b8b227fe8e75ce0ac15b1ee5a9cf87d07ea85229..2bbe156788683f3d079a2c2e543a88a830d657a5 100644
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -49,3 +49,4 @@ support-files =
 [test_window_extensible.html]
 [test_window_indexing.html]
 [test_writable-replaceable.html]
+[test_domwindowutils.html]
diff --git a/dom/base/test/test_domwindowutils.html b/dom/base/test/test_domwindowutils.html
new file mode 100644
index 0000000000000000000000000000000000000000..818144ebc32f9684c6710d821bcf060cf476e57e
--- /dev/null
+++ b/dom/base/test/test_domwindowutils.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Test for DOMWindowUtils</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var utils = SpecialPowers.getDOMWindowUtils(window);
+function test_sendMouseEventDefaults() {
+  var x = 1, y = 2, button = 1, clickCount = 2,
+      modifiers = SpecialPowers.Ci.nsIDOMNSEvent.SHIFT_MASK;
+
+  window.addEventListener("mousedown", function listener(evt) {
+    window.removeEventListener("mousedown", listener);
+    // Mandatory args
+    is(evt.clientX, x, "check x");
+    is(evt.clientY, y, "check y");
+    is(evt.button, button, "check button");
+    is(evt.detail, clickCount, "check click count");
+    is(evt.getModifierState("Shift"), true, "check modifiers");
+
+    // Default value for optionals
+    is(evt.mozPressure, 0, "check pressure");
+    is(evt.mozInputSource, SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE, "check input source");
+    is(evt.isSynthesized, undefined, "check isSynthesized is undefined in content");
+    is(SpecialPowers.wrap(evt).isSynthesized, true, "check isSynthesized is true from chrome");
+    next();
+  });
+
+  // Only pass mandatory arguments and check default values
+  utils.sendMouseEvent("mousedown", x, y, button, clickCount, modifiers);
+}
+
+function test_sendMouseEventOptionals() {
+  var x = 1, y = 2, button = 1, clickCount = 3,
+      modifiers = SpecialPowers.Ci.nsIDOMNSEvent.SHIFT_MASK,
+      pressure = 0.5,
+      source = SpecialPowers.Ci.nsIDOMMouseEvent.MOZ_SOURCE_KEYBOARD;
+
+  window.addEventListener("mouseup", function listener(evt) {
+    window.removeEventListener("mouseup", listener);
+    is(evt.mozInputSource, source, "explicit input source is valid");
+    is(SpecialPowers.wrap(evt).isSynthesized, false, "we can dispatch event that don't look synthesized");
+    next();
+  });
+
+  // Check explicit value for optional args
+  utils.sendMouseEvent("mouseup", x, y, button, clickCount, modifiers,
+                       false, pressure, source, false);
+}
+
+var tests = [
+  test_sendMouseEventDefaults,
+  test_sendMouseEventOptionals
+];
+
+function next() {
+  if (!tests.length) {
+    SimpleTest.finish();
+    return;
+  }
+
+  var test = tests.shift();
+  test();
+}
+
+function start() {
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.executeSoon(next);
+}
+
+window.addEventListener("load", start);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/dom/events/nsDOMEvent.h b/dom/events/nsDOMEvent.h
index fbcd9341d49800f07161f2d271ccc38840af0d75..5f4993b21cbc04c3e2d422583a010de55021b723 100644
--- a/dom/events/nsDOMEvent.h
+++ b/dom/events/nsDOMEvent.h
@@ -179,6 +179,11 @@ public:
     return mEvent->mFlags.mIsTrusted;
   }
 
+  bool IsSynthesized() const
+  {
+    return mEvent->mFlags.mIsSynthesizedForTests;
+  }
+
   uint64_t TimeStamp() const
   {
     return mEvent->time;
diff --git a/dom/inputmethod/mochitest/test_basic.html b/dom/inputmethod/mochitest/test_basic.html
index 8dd3091a76fa3e363f70b25d89d70b6f2fb7ea25..75cbdbbebdf008972b4b78bed9b41f14669f452a 100644
--- a/dom/inputmethod/mochitest/test_basic.html
+++ b/dom/inputmethod/mochitest/test_basic.html
@@ -118,11 +118,8 @@ function test_setComposition() {
 
 function test_endComposition() {
   gContext.endComposition('2013').then(function() {
-    if (gContext.textBeforeCursor + gContext.textAfterCursor == 'Xulei2013') {
-      ok(true, 'endComposition changed the input field correctly.');
-    } else {
-      todo(false, 'endComposition changed the input field incorrectly.');
-    }
+    is(gContext.textBeforeCursor + gContext.textAfterCursor, 'Xulei2013',
+       'endComposition changed the input field correctly.');
     test_onSelectionChange();
   }, function (e) {
     ok(false, 'endComposition failed: ' + e.name);
diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl
index e6b8180114f6083c8a363ef739e94accc2a806f1..22c1452ddfcd18ff69174e3ad769084835fe5628 100644
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -43,7 +43,7 @@ interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsICompositionStringSynthesizer;
 
-[scriptable, uuid(c6efd629-7282-4f0d-9db8-0fa59c191dd5)]
+[scriptable, uuid(fa0fe174-7c07-11e3-a5ba-000c290c393e)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
@@ -249,9 +249,13 @@ interface nsIDOMWindowUtils : nsISupports {
    * @param aPressure touch input pressure: 0.0 -> 1.0
    * @param aInputSourceArg input source, see nsIDOMMouseEvent for values,
    *        defaults to mouse input.
+   * @param aIsSynthesized controls nsIDOMEvent.isSynthesized value
+   *                       that helps identifying test related events,
+   *                       defaults to true
    *
    * returns true if the page called prevent default on this event
    */
+  [optional_argc]
   boolean sendMouseEvent(in AString aType,
                          in float aX,
                          in float aY,
@@ -260,7 +264,8 @@ interface nsIDOMWindowUtils : nsISupports {
                          in long aModifiers,
                          [optional] in boolean aIgnoreRootScrollFrame,
                          [optional] in float aPressure,
-                         [optional] in unsigned short aInputSourceArg);
+                         [optional] in unsigned short aInputSourceArg,
+                         [optional] in boolean aIsSynthesized);
 
   /** Synthesize a touch event. The event types supported are:
    *    touchstart, touchend, touchmove, and touchcancel
@@ -303,6 +308,7 @@ interface nsIDOMWindowUtils : nsISupports {
   /** The same as sendMouseEvent but ensures that the event is dispatched to
    *  this DOM window or one of its children.
    */
+  [optional_argc]
   void sendMouseEventToWindow(in AString aType,
                               in float aX,
                               in float aY,
@@ -311,7 +317,8 @@ interface nsIDOMWindowUtils : nsISupports {
                               in long aModifiers,
                               [optional] in boolean aIgnoreRootScrollFrame,
                               [optional] in float aPressure,
-                              [optional] in unsigned short aInputSourceArg);
+                              [optional] in unsigned short aInputSourceArg,
+                              [optional] in boolean aIsSynthesized);
 
   /** The same as sendTouchEvent but ensures that the event is dispatched to
    *  this DOM window or one of its children.
diff --git a/dom/ipc/TabChild.cpp b/dom/ipc/TabChild.cpp
index b08d0a5814cc43abf2e2dfcdca04fce9da15febb..40af3079f3d5f3be79087212d2163b7a59031e0f 100644
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2387,7 +2387,7 @@ TabChild::DispatchMouseEvent(const nsString&       aType,
   
   bool defaultPrevented = false;
   utils->SendMouseEvent(aType, aPoint.x, aPoint.y, aButton, aClickCount, aModifiers,
-                        aIgnoreRootScrollFrame, 0, aInputSourceArg, &defaultPrevented);
+                        aIgnoreRootScrollFrame, 0, aInputSourceArg, false, 4, &defaultPrevented);
   return defaultPrevented;
 }
 
diff --git a/dom/ipc/TabParent.cpp b/dom/ipc/TabParent.cpp
index 2ba6c16109acaaa22d1b01bd7224b917641fd00d..16dcea38413b38600baa48f308114d25e1707afe 100644
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1116,7 +1116,7 @@ TabParent::SendCompositionEvent(WidgetCompositionEvent& event)
   mIMECompositionStart = std::min(mIMESelectionAnchor, mIMESelectionFocus);
   if (mIMECompositionEnding)
     return true;
-  event.seqno = ++mIMESeqno;
+  event.mSeqno = ++mIMESeqno;
   return PBrowserParent::SendCompositionEvent(event);
 }
 
@@ -1147,7 +1147,7 @@ TabParent::SendTextEvent(WidgetTextEvent& event)
   mIMESelectionAnchor = mIMESelectionFocus =
       mIMECompositionStart + event.theText.Length();
 
-  event.seqno = ++mIMESeqno;
+  event.mSeqno = ++mIMESeqno;
   return PBrowserParent::SendTextEvent(event);
 }
 
@@ -1159,7 +1159,7 @@ TabParent::SendSelectionEvent(WidgetSelectionEvent& event)
   }
   mIMESelectionAnchor = event.mOffset + (event.mReversed ? event.mLength : 0);
   mIMESelectionFocus = event.mOffset + (!event.mReversed ? event.mLength : 0);
-  event.seqno = ++mIMESeqno;
+  event.mSeqno = ++mIMESeqno;
   return PBrowserParent::SendSelectionEvent(event);
 }
 
diff --git a/dom/messages/SystemMessagePermissionsChecker.jsm b/dom/messages/SystemMessagePermissionsChecker.jsm
index e4de5ab0ecf35200e1d2499b91cb886f067b34c8..74e550e17ae1c6ec6d26377086494cc1b979d6a5 100644
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -113,6 +113,7 @@ this.SystemMessagePermissionsTable = {
   "nfc-powerlevel-change": {
     "settings": ["read", "write"]
   },
+  "wifip2p-pairing-request": { },
 };
 
 this.SystemMessagePermissionsChecker = {
diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js
index 362010e387aa83ce78a33e1a4d66067836602fa0..4bd47353c5b80cb0ea14eebde8b567509841e6f0 100644
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -46,6 +46,11 @@ function debug(s) {
 let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND =
   libcutils.property_get("ro.moz.ril.data_reg_on_demand", "false") == "true";
 
+// Ril quirk to always turn the radio off for the client without SIM card
+// except hw default client.
+let RILQUIRKS_RADIO_OFF_WO_CARD =
+  libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true";
+
 const RADIOINTERFACELAYER_CID =
   Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
 const RADIOINTERFACE_CID =
@@ -90,6 +95,7 @@ const DOM_MOBILE_MESSAGE_DELIVERY_ERROR    = "error";
 
 const RADIO_POWER_OFF_TIMEOUT = 30000;
 const SMS_HANDLED_WAKELOCK_TIMEOUT = 5000;
+const HW_DEFAULT_CLIENT_ID = 0;
 
 const RIL_IPC_MOBILECONNECTION_MSG_NAMES = [
   "RIL:GetRilContext",
@@ -502,6 +508,8 @@ XPCOMUtils.defineLazyGetter(this, "gMessageManager", function() {
 
 XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
   return {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
+
     ril: null,
     pendingMessages: [],  // For queueing "RIL:SetRadioEnabled" messages.
     timer: null,
@@ -510,6 +518,7 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
 
     init: function(ril) {
       this.ril = ril;
+      Services.obs.addObserver(this, kSysMsgListenerReadyObserverTopic, false);
     },
 
     receiveMessage: function(msg) {
@@ -541,9 +550,47 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
       this._handleMessage(msg);
     },
 
+    _getNumCards: function() {
+      let numCards = 0;
+      for (let i = 0, N = this.ril.numRadioInterfaces; i < N; ++i) {
+        if (this._isCardPresentAtClient(i)) {
+          numCards++;
+        }
+      }
+      return numCards;
+    },
+
+    _isCardPresentAtClient: function(clientId) {
+      let cardState = this.ril.getRadioInterface(clientId).rilContext.cardState;
+      return cardState !== RIL.GECKO_CARDSTATE_UNDETECTED &&
+        cardState !== RIL.GECKO_CARDSTATE_UNKNOWN;
+    },
+
+    _isRadioAbleToEnableAtClient: function(clientId, numCards) {
+      if (!RILQUIRKS_RADIO_OFF_WO_CARD) {
+        return true;
+      }
+
+      // We could only turn on the radio for clientId if
+      // 1. a SIM card is presented or
+      // 2. it is the default clientId and there is no any SIM card at any client.
+
+      if (this._isCardPresentAtClient(clientId)) {
+        return true;
+      }
+
+      numCards = numCards == null ? this._getNumCards() : numCards;
+      if (clientId === HW_DEFAULT_CLIENT_ID && numCards === 0) {
+        return true;
+      }
+
+      return false;
+    },
+
     _handleMessage: function(msg) {
       if (DEBUG) debug("setRadioEnabled: handleMessage: " + JSON.stringify(msg));
-      let radioInterface = this.ril.getRadioInterface(msg.json.clientId || 0);
+      let clientId = msg.json.clientId || 0;
+      let radioInterface = this.ril.getRadioInterface(clientId);
 
       if (!radioInterface.isValidStateForSetRadioEnabled()) {
         radioInterface.setRadioEnabledResponse(msg.target, msg.json.data,
@@ -559,7 +606,13 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
       }
 
       if (msg.json.data.enabled) {
-        radioInterface.receiveMessage(msg);
+        if (this._isRadioAbleToEnableAtClient(clientId)) {
+          radioInterface.receiveMessage(msg);
+        } else {
+          // Not really do it but respond success.
+          radioInterface.setRadioEnabledResponse(msg.target, msg.json.data);
+        }
+
         this._processNextMessage();
       } else {
         this.request = (function() {
@@ -620,6 +673,27 @@ XPCOMUtils.defineLazyGetter(this, "gRadioEnabledController", function() {
         this.request = null;
       }
       this._processNextMessage();
+    },
+
+    /**
+     * nsIObserver interface methods.
+     */
+    observe: function observe(subject, topic, data) {
+      switch (topic) {
+        case kSysMsgListenerReadyObserverTopic:
+          Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic);
+
+          let numCards = this._getNumCards();
+          for (let i = 0, N = this.ril.numRadioInterfaces; i < N; ++i) {
+            if (this._isRadioAbleToEnableAtClient(i, numCards)) {
+              let radioInterface = this.ril.getRadioInterface(i);
+              radioInterface.setRadioEnabledInternal({enabled: true}, null);
+            }
+          }
+          break;
+        default:
+          break;
+      }
     }
   };
 });
@@ -1181,7 +1255,6 @@ function RadioInterface(options) {
 
   Services.obs.addObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
-  Services.obs.addObserver(this, kSysMsgListenerReadyObserverTopic, false);
   Services.obs.addObserver(this, kSysClockChangeObserverTopic, false);
   Services.obs.addObserver(this, kScreenStateChangedTopic, false);
 
@@ -2659,9 +2732,6 @@ RadioInterface.prototype = {
 
   observe: function(subject, topic, data) {
     switch (topic) {
-      case kSysMsgListenerReadyObserverTopic:
-        this.setRadioEnabledInternal({enabled: true}, null);
-        break;
       case kMozSettingsChangedObserverTopic:
         let setting = JSON.parse(data);
         this.handleSettingsChange(setting.key, setting.value, setting.message);
@@ -3248,6 +3318,15 @@ RadioInterface.prototype = {
   _fragmentText7Bit: function(text, langTable, langShiftTable, segmentSeptets, strict7BitEncoding) {
     let ret = [];
     let body = "", len = 0;
+    // If the message is empty, we only push the empty message to ret.
+    if (text.length == 0) {
+      ret.push({
+        body: text,
+        encodedBodyLength: text.length,
+      });
+      return ret;
+    }
+
     for (let i = 0, inc = 0; i < text.length; i++) {
       let c = text.charAt(i);
       if (strict7BitEncoding) {
@@ -3317,6 +3396,15 @@ RadioInterface.prototype = {
    */
   _fragmentTextUCS2: function(text, segmentChars) {
     let ret = [];
+    // If the message is empty, we only push the empty message to ret.
+    if (text.length == 0) {
+      ret.push({
+        body: text,
+        encodedBodyLength: text.length,
+      });
+      return ret;
+    }
+
     for (let offset = 0; offset < text.length; offset += segmentChars) {
       let str = text.substr(offset, segmentChars);
       ret.push({
diff --git a/dom/system/gonk/nsINetworkManager.idl b/dom/system/gonk/nsINetworkManager.idl
index 77c8e4ae902fc22df63bb8d6046501cf2c4301bc..acd9c7eadf628de060a41412ae822ba3d78ecbc5 100644
--- a/dom/system/gonk/nsINetworkManager.idl
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -9,7 +9,7 @@ interface nsIWifiTetheringCallback;
 /**
  * Information about networks that is exposed to network manager API consumers.
  */
-[scriptable, uuid(f4cf9d88-f962-4d29-9baa-fb295dad387b)]
+[scriptable, uuid(e2f5c6e0-4203-11e3-aa6e-0800200c9a66)]
 interface nsINetworkInterface : nsISupports
 {
   const long NETWORK_STATE_UNKNOWN = -1;
@@ -31,6 +31,7 @@ interface nsINetworkInterface : nsISupports
   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;
 
   /**
    * Network type. One of the NETWORK_TYPE_* constants.
diff --git a/dom/telephony/Telephony.cpp b/dom/telephony/Telephony.cpp
index 0de1b3eb65184eb4c005d45c93bcc5e0dc020633..cbde08f20aff919376eba022615ebc48759c6b6e 100644
--- a/dom/telephony/Telephony.cpp
+++ b/dom/telephony/Telephony.cpp
@@ -697,11 +697,11 @@ Telephony::NotifyError(uint32_t aServiceId,
 NS_IMETHODIMP
 Telephony::NotifyCdmaCallWaiting(uint32_t aServiceId, const nsAString& aNumber)
 {
-  MOZ_ASSERT(mActiveCall &&
-             mActiveCall->ServiceId() == aServiceId &&
-             mActiveCall->CallState() == nsITelephonyProvider::CALL_STATE_CONNECTED);
+  MOZ_ASSERT(mCalls.Length() == 1);
+
+  nsRefPtr<TelephonyCall> callToNotify = mCalls[0];
+  MOZ_ASSERT(callToNotify && callToNotify->ServiceId() == aServiceId);
 
-  nsRefPtr<TelephonyCall> callToNotify = mActiveCall;
   callToNotify->UpdateSecondNumber(aNumber);
   DispatchCallEvent(NS_LITERAL_STRING("callschanged"), callToNotify);
   return NS_OK;
diff --git a/dom/tests/mochitest/general/test_interfaces.html b/dom/tests/mochitest/general/test_interfaces.html
index 3603136ccfb8075dbade0a8f119abbad6b20ceab..e0bdda4a588379d491c6b083e9304c9c66c5313d 100644
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -692,6 +692,12 @@ var interfaceNamesInGlobalScope =
     "MozWakeLock",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiConnectionInfoEvent", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozWifiP2pGroupOwner", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozWifiP2pManager", b2g: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "MozWifiP2pStatusChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "MozWifiStatusChangeEvent", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
diff --git a/dom/webidl/Event.webidl b/dom/webidl/Event.webidl
index 164415896d138700c9c78012573508809241a7a7..bc5f1f0e5fb6bd9af9db69eb4180b3dfb3c6dd1a 100644
--- a/dom/webidl/Event.webidl
+++ b/dom/webidl/Event.webidl
@@ -56,6 +56,7 @@ partial interface Event {
   readonly attribute EventTarget? originalTarget;
   readonly attribute EventTarget? explicitOriginalTarget;
   [ChromeOnly] readonly attribute boolean multipleActionsPrevented;
+  [ChromeOnly] readonly attribute boolean isSynthesized;
 
   boolean getPreventDefault();
 };
diff --git a/dom/webidl/MozWifiP2pManager.webidl b/dom/webidl/MozWifiP2pManager.webidl
new file mode 100644
index 0000000000000000000000000000000000000000..400fa1fd80c14cdc3541a255bb5c011db590ca32
--- /dev/null
+++ b/dom/webidl/MozWifiP2pManager.webidl
@@ -0,0 +1,146 @@
+/* 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/. */
+
+enum WPSMethod {
+  "pbc",
+  "keypad",
+  "display"
+};
+
+dictionary WPSInfo {
+  WPSMethod method;
+  DOMString pin;
+};
+
+[JSImplementation="@mozilla.org/wifip2pgroupowner;1"]
+interface MozWifiP2pGroupOwner {
+  readonly attribute DOMString groupName;
+  readonly attribute DOMString macAddress;
+  readonly attribute DOMString ipAddress;
+  readonly attribute DOMString passphrase;
+  readonly attribute DOMString ssid;
+  readonly attribute any wpsCapabilities;
+  readonly attribute unsigned long freq;
+  readonly attribute boolean isLocal;
+};
+
+[JSImplementation="@mozilla.org/wifip2pmanager;1",
+ NavigatorProperty="mozWifiP2pManager",
+ Func="Navigator::HasWifiManagerSupport"]
+interface MozWifiP2pManager : EventTarget
+{
+  /**
+   * Enable/Disable wifi direct scan.
+   *
+   * onsuccess: Succeeded in starting/stopping wifi direct scan.
+   * onerror:   Failed to start/stop wifi direct scan.
+   *
+   */
+  DOMRequest setScanEnabled(boolean enabled);
+
+  /**
+   * Connect to a peer with given configuration.
+   *
+   * @param address The peer MAC address we are going to connect.
+   * @param wpsMethod The WPS method we want to use.
+   * @param goIntent Number from 0 ~ 15 to indicate how much we want to be
+   *                 the group owner.
+   *
+   * onsuccess: Succeeded in issueing a 'connect' request. It doesn't mean we
+   *            have connected to the peer.
+   *
+   * onerror:   Failed to issue a 'connect' request, probably due to an
+   *            invalid peer address, unsupported wps method or any
+   *            preliminary error.
+   *
+   **/
+  DOMRequest connect(DOMString address, WPSMethod wpsMethod, optional byte goIntent);
+
+  /**
+   * Disconnect with a peer.
+   *
+   * @param address The mac address of the peer.
+   *
+   * onsuccess: Succeeded to issue a 'disconnect' request. It doesn't mean we
+   *            have disconnected with the peer.
+   *
+   * onerror:   Failed to issue a 'disconnect' request, probably due to the
+   *            invalid peer address or any preliminary error.
+   *
+   */
+  DOMRequest disconnect(DOMString address);
+
+  /**
+   * Get peer list
+   *
+   * onsuccess: Command success, req.result contains an array of peer objects.
+   * onerror: Command failed.
+   *
+   * Peer object format:
+   *   .address          MAC address of the peer (string)
+   *   .name             the peer's device name (string)
+   *   .isGroupOwner     if the peer is the group owner (boolean)
+   *   .wpsCapabilities  array of the supported |WPSMethod|
+   *   .connectionStatus one of { "disconnected", "connecting", "connected", "disconnecting" }
+   *
+   */
+  DOMRequest getPeerList();
+
+  /**
+   * Set pairing confirmation result.
+   *
+   * @param accepted Boolean to indicate whether we accepted the request or not.
+   * @param pin The user input pin number if the wps method is keypad.
+   *
+   * onsuccess: Command succeeded.
+   * onerror:   Command failed.
+   *
+   */
+  DOMRequest setPairingConfirmation(boolean accepted, optional DOMString pin);
+
+  /**
+   * Set device name.
+   *
+   * @param devieName The new device name we are going to set.
+   *
+   * onsuccess: Command succeeded.
+   * onerror:   Command failed.
+   *
+   */
+  DOMRequest setDeviceName(DOMString deviceName);
+
+  /**
+   * Returns if Wifi Direct is enabled.
+   *
+   */
+  readonly attribute boolean enabled;
+
+  /**
+   * The current group owner, null if none.
+   */
+  readonly attribute MozWifiP2pGroupOwner? groupOwner;
+
+  /**
+   * An event listener that is called whenever the Wifi Direct peer list is
+   * updated. Use getPeerList() to get the up-to-date peer list.
+   */
+  attribute EventHandler onpeerinfoupdate;
+
+  /**
+   * An event listener that is called whenever Wifi Direct status changed.
+   * The address of the changed peer will be stored in event.peerList.
+   * See MozWifiP2pStatusChangeEvent.webidl.
+   */
+  attribute EventHandler onstatuschange;
+
+  /**
+   * An event listener that is called whenever Wifi Direct is enabled.
+   */
+  attribute EventHandler onenabled;
+
+  /**
+   * An event listener that is called whenever Wifi Direct is disabled.
+   */
+  attribute EventHandler ondisabled;
+};
diff --git a/dom/webidl/MozWifiP2pStatusChangeEvent.webidl b/dom/webidl/MozWifiP2pStatusChangeEvent.webidl
new file mode 100644
index 0000000000000000000000000000000000000000..24fbc4b37d29b08b13cb18289c61003937386f48
--- /dev/null
+++ b/dom/webidl/MozWifiP2pStatusChangeEvent.webidl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ */
+
+[Constructor(DOMString type, optional MozWifiP2pStatusChangeEventInit eventInitDict), HeaderFile="GeneratedEventClasses.h"]
+interface MozWifiP2pStatusChangeEvent : Event
+{
+  readonly attribute DOMString peerAddress;
+};
+
+dictionary MozWifiP2pStatusChangeEventInit : EventInit
+{
+  DOMString peerAddress = "";
+};
diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build
index 5571d05faba02c9b0e677ea92affc2a9237e4019..e7b8d814b819bd23ecd78e0f3a59fb8d462f7eb1 100644
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -537,6 +537,8 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     WEBIDL_FILES += [
         'MozSpeakerManager.webidl',
         'MozWifiConnectionInfoEvent.webidl',
+        'MozWifiP2pManager.webidl',
+        'MozWifiP2pStatusChangeEvent.webidl',
         'MozWifiStatusChangeEvent.webidl',
     ]
 
diff --git a/dom/wifi/DOMWifiP2pManager.js b/dom/wifi/DOMWifiP2pManager.js
new file mode 100644
index 0000000000000000000000000000000000000000..a485035a0a589b1ca40f7549cbc66af8dcd066a5
--- /dev/null
+++ b/dom/wifi/DOMWifiP2pManager.js
@@ -0,0 +1,323 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+
+const DEBUG = false;
+
+// interface MozWifiP2pGroupOwner implementation.
+
+function MozWifiP2pGroupOwner(aGo) {
+  this.groupName = aGo.groupName;
+  this.macAddress = aGo.macAddress;
+  this.ipAddress = aGo.ipAddress;
+  this.passphrase = aGo.passphrase;
+  this.ssid = aGo.ssid;
+  this.wpsCapabilities = aGo.wpsCapabilities;
+  this.freq = aGo.freq;
+  this.isLocal = aGo.isLocal;
+}
+
+MozWifiP2pGroupOwner.prototype = {
+  classID: Components.ID("{a9b81450-349d-11e3-aa6e-0800200c9a66}"),
+  contractID: "@mozilla.org/wifip2pgroupowner;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports])
+};
+
+// interface MozWifiP2pManager implementation.
+
+const MOZ_WIFIP2PMANAGER_CONTRACTID = "@mozilla.org/wifip2pmanager;1";
+const MOZ_WIFIP2PMANAGER_CID        = Components.ID("{8d9125a0-3498-11e3-aa6e-0800200c9a66}");
+
+function MozWifiP2pManager() {
+  this.defineEventHandlerGetterSetter("onstatuschange");
+  this.defineEventHandlerGetterSetter("onpeerinfoupdate");
+  this.defineEventHandlerGetterSetter("onenabled");
+  this.defineEventHandlerGetterSetter("ondisabled");
+
+  this.currentPeer = null;
+  this.enabled = false;
+  this.groupOwner = null;
+}
+
+// For smaller, read-only APIs, we expose any property that doesn't begin with
+// an underscore.
+function exposeReadOnly(obj) {
+  let exposedProps = {};
+  for (let i in obj) {
+    if (i[0] === "_") {
+      continue;
+    }
+    exposedProps[i] = "r";
+  }
+
+  obj.__exposedProps__ = exposedProps;
+  return obj;
+}
+
+function debug(msg) {
+  if (DEBUG) {
+    dump('-------------- MozWifiP2pManager: ' + msg);
+  }
+}
+
+MozWifiP2pManager.prototype = {
+  __proto__: DOMRequestIpcHelper.prototype,
+
+  classID:        MOZ_WIFIP2PMANAGER_CID,
+  contractID:     MOZ_WIFIP2PMANAGER_CONTRACTID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver,
+                                         Ci.nsISupports]),
+
+  //
+  // nsIDOMGlobalPropertyInitializer implementation.
+  //
+
+  init: function(aWindow) {
+    const messages = ["WifiP2pManager:setScanEnabled:Return:OK",
+                      "WifiP2pManager:setScanEnabled:Return:NO",
+                      "WifiP2pManager:getPeerList:Return:OK",
+                      "WifiP2pManager:getPeerList:Return:NO",
+                      "WifiP2pManager:connect:Return:OK",
+                      "WifiP2pManager:connect:Return:NO",
+                      "WifiP2pManager:disconnect:Return:OK",
+                      "WifiP2pManager:disconnect:Return:NO",
+                      "WifiP2pManager:setPairingConfirmation:Return",
+                      "WifiP2pManager:setDeviceName:Return:OK",
+                      "WifiP2pManager:setDeviceName:Return:NO",
+
+                      "WifiP2pManager:p2pDown",
+                      "WifiP2pManager:p2pUp",
+                      "WifiP2pManager:onconnecting",
+                      "WifiP2pManager:onconnected",
+                      "WifiP2pManager:ondisconnected",
+                      "WifiP2pManager:ongroupnstop",
+                      "WifiP2pManager:onconnectingfailed",
+                      "WifiP2pManager:onwpstimeout",
+                      "WifiP2pManager:onwpsfail",
+                      "WifiP2pManager:onpeerinfoupdate",
+                      ];
+
+    this.initDOMRequestHelper(aWindow, messages);
+    this._mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsISyncMessageSender);
+
+    // Notify the internal a new DOM mananger is created.
+    let state = this._mm.sendSyncMessage("WifiP2pManager:getState")[0];
+    if (state) {
+      debug('State: ' + JSON.stringify(state));
+    } else {
+      debug('Failed to get state');
+    }
+  },
+
+  uninit: function() {
+  },
+
+  _sendMessageForRequest: function(name, data, request) {
+    let id = this.getRequestId(request);
+    this._mm.sendAsyncMessage(name, { data: data, rid: id, mid: this._id });
+  },
+
+  receiveMessage: function(aMessage) {
+    let msg = aMessage.json;
+    if (msg.mid && msg.mid !== this._id) {
+      return;
+    }
+
+    let request;
+    switch (aMessage.name) {
+     case "WifiP2pManager:setScanEnabled:Return:OK":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+        break;
+
+      case "WifiP2pManager:setScanEnabled:Return:NO":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireError(request, "Unable to enable/disable Wifi P2P peer discovery.");
+        break;
+
+     case "WifiP2pManager:getPeerList:Return:OK":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireSuccess(request, msg.data);
+        break;
+
+      case "WifiP2pManager:getPeerList:Return:NO":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireError(request, "Unable to disable Wifi P2P peer discovery.");
+        break;
+
+      case "WifiP2pManager:connect:Return:OK":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+        break;
+
+      case "WifiP2pManager:connect:Return:NO":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireError(request, "Unable to connect to Wifi P2P peer.");
+        break;
+
+      case "WifiP2pManager:disconnect:Return:OK":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+        break;
+
+      case "WifiP2pManager:disconnect:Return:NO":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireError(request, "Unable to disconnect to Wifi P2P peer.");
+        break;
+
+      case "WifiP2pManager:setDeviceName:Return:OK":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireSuccess(request, exposeReadOnly(msg.data));
+        break;
+
+      case "WifiP2pManager:setDeviceName:Return:NO":
+        request = this.takeRequest(msg.rid);
+        Services.DOMRequest.fireError(request, "Unable to set device name.");
+        break;
+
+      case "WifiP2pManager:p2pDown":
+        this.enabled = false;
+        this.currentPeer = null;
+        this._fireEnabledOrDisabled(false);
+        break;
+
+      case "WifiP2pManager:p2pUp":
+        this.enabled = true;
+        this._fireEnabledOrDisabled(true);
+        break;
+
+      case "WifiP2pManager:onconnecting":
+        debug('onconnecting with peer: ' + JSON.stringify(msg.peer));
+        this.currentPeer = msg.peer;
+        this._fireStatusChangeEvent(msg.peer.address);
+        break;
+
+      case "WifiP2pManager:onconnected":
+        debug('onconnected with peer: ' + JSON.stringify(msg.peer));
+        this.currentPeer = msg.peer;
+        this.groupOwner = new MozWifiP2pGroupOwner(msg.groupOwner);
+        this._fireStatusChangeEvent(msg.peer.address);
+        break;
+
+      case "WifiP2pManager:ondisconnected":
+        debug('ondisconnected with peer: ' + JSON.stringify(msg.peer));
+        this.currentPeer = null;
+        this.groupOwner = null;
+        this._fireStatusChangeEvent(msg.peer.address);
+        break;
+
+      case "WifiP2pManager:onconnectingfailed":
+        this._fireStatusChangeEvent(null);
+        break;
+
+      case "WifiP2pManager:onwpstimeout":
+        this._fireStatusChangeEvent(null);
+        break;
+
+      case "WifiP2pManager:onwpsfail":
+        this._fireStatusChangeEvent(null);
+        break;
+
+      case "WifiP2pManager:onpeerinfoupdate":
+        this._firePeerInfoUpdateEvent();
+        break;
+    }
+  },
+
+  _firePeerInfoUpdateEvent: function PeerInfoUpdate() {
+    let evt = new this._window.Event("peerinfoupdate");
+    this.__DOM_IMPL__.dispatchEvent(evt);
+  },
+
+  _fireStatusChangeEvent: function WifiP2pStatusChange(peerAddress) {
+    let evt = new this._window.MozWifiP2pStatusChangeEvent("statuschange",
+                                                           { peerAddress: peerAddress });
+    this.__DOM_IMPL__.dispatchEvent(evt);
+  },
+
+  _fireEnabledOrDisabled: function enabledDisabled(enabled) {
+    let evt = new this._window.Event(enabled ? "enabled" : "disabled");
+    this.__DOM_IMPL__.dispatchEvent(evt);
+  },
+
+  //
+  // WifiP2pManager.webidl implementation.
+  //
+
+  enableScan: function () {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:enableScan", null, request);
+    return request;
+  },
+
+  disableScan: function () {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:disableScan", null, request);
+    return request;
+  },
+
+  setScanEnabled: function(enabled) {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:setScanEnabled", enabled, request);
+    return request;
+  },
+
+  connect: function (address, wpsMethod, goIntent) {
+    let request = this.createRequest();
+    let connectionInfo = { address: address, wpsMethod: wpsMethod, goIntent: goIntent };
+    this._sendMessageForRequest("WifiP2pManager:connect", connectionInfo, request);
+    return request;
+  },
+
+  disconnect: function (address) {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:disconnect", address, request);
+    return request;
+  },
+
+  getPeerList: function () {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:getPeerList", null, request);
+    return request;
+  },
+
+  setPairingConfirmation: function (accepted, pin) {
+    let request = this.createRequest();
+    let result = { accepted: accepted, pin: pin };
+    this._sendMessageForRequest("WifiP2pManager:setPairingConfirmation", result, request);
+    return request;
+  },
+
+  setDeviceName: function(newDeviceName) {
+    let request = this.createRequest();
+    this._sendMessageForRequest("WifiP2pManager:setDeviceName", newDeviceName, request);
+    return request;
+  },
+
+  // Helpers.
+  defineEventHandlerGetterSetter: function(event) {
+    Object.defineProperty(this, event, {
+      get: function() {
+        return this.__DOM_IMPL__.getEventHandler(event);
+      },
+
+      set: function(handler) {
+        this.__DOM_IMPL__.setEventHandler(event, handler);
+      }
+    });
+  },
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MozWifiP2pManager, MozWifiP2pGroupOwner]);
diff --git a/dom/wifi/DOMWifiP2pManager.manifest b/dom/wifi/DOMWifiP2pManager.manifest
new file mode 100644
index 0000000000000000000000000000000000000000..bc80efa489b91cc7d708f849bb284c2e70f6ab16
--- /dev/null
+++ b/dom/wifi/DOMWifiP2pManager.manifest
@@ -0,0 +1,6 @@
+# DOMWifiP2pManager.js
+component {8d9125a0-3498-11e3-aa6e-0800200c9a66} DOMWifiP2pManager.js
+contract @mozilla.org/wifip2pmanager;1 {8d9125a0-3498-11e3-aa6e-0800200c9a66}
+
+component {a9b81450-349d-11e3-aa6e-0800200c9a66} DOMWifiP2pManager.js
+contract @mozilla.org/wifip2pgroupowner;1 {a9b81450-349d-11e3-aa6e-0800200c9a66}
\ No newline at end of file
diff --git a/dom/wifi/StateMachine.jsm b/dom/wifi/StateMachine.jsm
new file mode 100644
index 0000000000000000000000000000000000000000..ab743a30348eff8c216189c45bdc5104ccbbdee5
--- /dev/null
+++ b/dom/wifi/StateMachine.jsm
@@ -0,0 +1,205 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+this.EXPORTED_SYMBOLS = ["StateMachine"];
+
+const DEBUG = false;
+
+this.StateMachine = function(aDebugTag) {
+  function debug(aMsg) {
+    dump('-------------- StateMachine:' + aDebugTag + ': ' + aMsg);
+  }
+
+  var sm = {};
+
+  var _initialState;
+  var _curState;
+  var _prevState;
+  var _paused;
+  var _eventQueue = [];
+  var _deferredEventQueue = [];
+  var _defaultEventHandler;
+
+  // Public interfaces.
+
+  sm.setDefaultEventHandler = function(aDefaultEventHandler) {
+    _defaultEventHandler = aDefaultEventHandler;
+  };
+
+  sm.start = function(aInitialState) {
+    _initialState = aInitialState;
+    sm.gotoState(_initialState);
+  };
+
+  sm.sendEvent = function (aEvent) {
+    if (!_initialState) {
+      if (DEBUG) {
+        debug('StateMachine is not running. Call StateMachine.start() first.');
+      }
+      return;
+    }
+    _eventQueue.push(aEvent);
+    asyncCall(handleFirstEvent);
+  };
+
+  sm.getPreviousState = function() {
+    return _prevState;
+  };
+
+  sm.getCurrentState = function() {
+    return _curState;
+  };
+
+  // State object maker.
+  // @param aName string for this state's name.
+  // @param aDelegate object:
+  //    .handleEvent: required.
+  //    .enter: called before entering this state (optional).
+  //    .exit: called before exiting this state (optional).
+  sm.makeState = function (aName, aDelegate) {
+    if (!aDelegate.handleEvent) {
+      throw "handleEvent is a required delegate function.";
+    }
+    var nop = function() {};
+    return {
+      name: aName,
+      enter: (aDelegate.enter || nop),
+      exit: (aDelegate.exit || nop),
+      handleEvent: aDelegate.handleEvent
+    };
+  };
+
+  sm.deferEvent = function (aEvent) {
+    // The definition of a 'deferred event' is:
+    //     We are not able to handle this event now but after receiving
+    //     certain event or entering a new state, we might be able to handle
+    //     it. For example, we couldn't handle CONNECT_EVENT in the
+    //     diconnecting state. But once we finish doing "disconnecting", we
+    //     could then handle CONNECT_EVENT!
+    //
+    // So, the deferred event may be handled in the following cases:
+    //     1. Once we entered a new state.
+    //     2. Once we handled a regular event.
+    if (DEBUG) {
+      debug('Deferring event: ' + JSON.stringify(aEvent));
+    }
+    _deferredEventQueue.push(aEvent);
+  };
+
+  // Goto the new state. If the current state is null, the exit
+  // function won't be called.
+  sm.gotoState = function (aNewState) {
+    if (_curState) {
+      if (DEBUG) {
+        debug("exiting state: " + _curState.name);
+      }
+      _curState.exit();
+    }
+
+    _prevState = _curState;
+    _curState = aNewState;
+
+    if (DEBUG) {
+      debug("entering state: " + _curState.name);
+    }
+    _curState.enter();
+
+    // We are in the new state now. We got a chance to handle the
+    // deferred events.
+    handleDeferredEvents();
+
+    sm.resume();
+  };
+
+  // No incoming event will be handled after you call pause().
+  // (But they will be queued.)
+  sm.pause = function() {
+    _paused = true;
+  };
+
+  // Continue to handle incoming events.
+  sm.resume = function() {
+    _paused = false;
+    asyncCall(handleFirstEvent);
+  };
+
+  //----------------------------------------------------------
+  // Private stuff
+  //----------------------------------------------------------
+
+  function asyncCall(f) {
+    Services.tm.currentThread.dispatch(f, Ci.nsIThread.DISPATCH_NORMAL);
+  }
+
+  function handleFirstEvent() {
+    var hadDeferredEvents;
+
+    if (0 === _eventQueue.length) {
+      return;
+    }
+
+    if (_paused) {
+      return; // The state machine is paused now.
+    }
+
+    hadDeferredEvents = _deferredEventQueue.length > 0;
+
+    handleOneEvent(_eventQueue.shift()); // The handler may defer this event.
+
+    // We've handled one event. If we had deferred events before, now is
+    // a good chance to handle them.
+    if (hadDeferredEvents) {
+      handleDeferredEvents();
+    }
+
+    // Continue to handle the next regular event.
+    handleFirstEvent();
+  }
+
+  function handleDeferredEvents() {
+    if (_deferredEventQueue.length && DEBUG) {
+      debug('Handle deferred events: ' + _deferredEventQueue.length);
+    }
+    for (let i = 0; i < _deferredEventQueue.length; i++) {
+      handleOneEvent(_deferredEventQueue.shift());
+    }
+  }
+
+  function handleOneEvent(aEvent)
+  {
+    if (DEBUG) {
+      debug('Handling event: ' + JSON.stringify(aEvent));
+    }
+
+    var handled = _curState.handleEvent(aEvent);
+
+    if (undefined === handled) {
+      throw "handleEvent returns undefined: " + _curState.name;
+    }
+    if (!handled) {
+      // Event is not handled in the current state. Try handleEventCommon().
+      handled = (_defaultEventHandler ? _defaultEventHandler(aEvent) : handled);
+    }
+    if (undefined === handled) {
+      throw "handleEventCommon returns undefined: " + _curState.name;
+    }
+    if (!handled) {
+      if (DEBUG) {
+        debug('!!!!!!!!! FIXME !!!!!!!!! Event not handled: ' + JSON.stringify(aEvent));
+      }
+    }
+
+    return handled;
+  }
+
+  return sm;
+};
diff --git a/dom/wifi/WifiCommand.jsm b/dom/wifi/WifiCommand.jsm
index b7a550566e6aade6a5b48e6a080615f6f25358fc..6456628e1394d0945669bc591b18ce87ea4f7aa5 100644
--- a/dom/wifi/WifiCommand.jsm
+++ b/dom/wifi/WifiCommand.jsm
@@ -14,8 +14,15 @@ Cu.import("resource://gre/modules/systemlibs.js");
 
 const SUPP_PROP = "init.svc.wpa_supplicant";
 const WPA_SUPPLICANT = "wpa_supplicant";
+const DEBUG = false;
 
 this.WifiCommand = function(aControlMessage, aInterface) {
+  function debug(msg) {
+    if (DEBUG) {
+      dump('-------------- WifiCommand: ' + msg);
+    }
+  }
+
   var command = {};
 
   //-------------------------------------------------
@@ -135,14 +142,16 @@ this.WifiCommand = function(aControlMessage, aInterface) {
     doStringCommand("LOG_LEVEL", callback);
   };
 
-  command.wpsPbc = function (callback) {
-    doBooleanCommand("WPS_PBC", "OK", callback);
+  command.wpsPbc = function (iface, callback) {
+    doBooleanCommand("WPS_PBC" + (iface ? (" interface=" + iface) : ""),
+                     "OK", callback);
   };
 
   command.wpsPin = function (detail, callback) {
     doStringCommand("WPS_PIN " +
                     (detail.bssid === undefined ? "any" : detail.bssid) +
-                    (detail.pin === undefined ? "" : (" " + detail.pin)),
+                    (detail.pin === undefined ? "" : (" " + detail.pin)) +
+                    (detail.iface ? (" interface=" + detail.iface) : ""),
                     callback);
   };
 
@@ -337,9 +346,89 @@ this.WifiCommand = function(aControlMessage, aInterface) {
     });
   };
 
-  //--------------------------------------------------
-  // Helper functions.
-  //--------------------------------------------------
+  command.setDeviceName = function(deviceName, callback) {
+    doBooleanCommand("SET device_name " + deviceName, "OK", callback);
+  };
+
+  //-------------------------------------------------
+  // P2P commands.
+  //-------------------------------------------------
+
+  command.p2pProvDiscovery = function(address, wpsMethod, callback) {
+    var command = "P2P_PROV_DISC " + address + " " + wpsMethod;
+    doBooleanCommand(command, "OK", callback);
+  };
+
+  command.p2pConnect = function(config, callback) {
+    var command = "P2P_CONNECT " + config.address + " " + config.wpsMethodWithPin + " ";
+    if (config.joinExistingGroup) {
+      command += "join";
+    } else {
+      command += "go_intent=" + config.goIntent;
+    }
+
+    debug('P2P connect command: ' + command);
+    doBooleanCommand(command, "OK", callback);
+  };
+
+  command.p2pGroupRemove = function(iface, callback) {
+    debug("groupRemove()");
+    doBooleanCommand("P2P_GROUP_REMOVE " + iface, "OK", callback);
+  };
+
+  command.p2pEnable = function(detail, callback) {
+    var commandChain = ["SET device_name "    + detail.deviceName,
+                        "SET device_type "    + detail.deviceType,
+                        "SET config_methods " + detail.wpsMethods,
+                        "P2P_SET conc_pref sta",
+                        "P2P_FLUSH"];
+
+    doBooleanCommandChain(commandChain, callback);
+  };
+
+  command.p2pDisable = function(callback) {
+    doBooleanCommand("P2P_SET disabled 1", "OK", callback);
+  };
+
+  command.p2pEnableScan = function(timeout, callback) {
+    doBooleanCommand("P2P_FIND " + timeout, "OK", callback);
+  };
+
+  command.p2pDisableScan = function(callback) {
+    doBooleanCommand("P2P_STOP_FIND", "OK", callback);
+  };
+
+  command.p2pGetGroupCapab = function(address, callback) {
+    command.p2pPeer(address, function(reply) {
+      debug('p2p_peer reply: ' + reply);
+      if (!reply) {
+        callback(0);
+        return;
+      }
+      var capab = /group_capab=0x([0-9a-fA-F]+)/.exec(reply)[1];
+      if (!capab) {
+        callback(0);
+      } else {
+        callback(parseInt(capab, 16));
+      }
+    });
+  };
+
+  command.p2pPeer = function(address, callback) {
+    doStringCommand("P2P_PEER " + address, callback);
+  };
+
+  command.p2pGroupAdd = function(netId, callback) {
+    doBooleanCommand("P2P_GROUP_ADD persistent=" + netId, callback);
+  };
+
+  command.p2pReinvoke = function(netId, address, callback) {
+    doBooleanCommand("P2P_INVITE persistent=" + netId + " peer=" + address, "OK", callback);
+  };
+
+  //----------------------------------------------------------
+  // Private stuff.
+  //----------------------------------------------------------
 
   function voidControlMessage(cmd, callback) {
     aControlMessage({ cmd: cmd, iface: aInterface }, function (data) {
@@ -391,6 +480,10 @@ this.WifiCommand = function(aControlMessage, aInterface) {
     });
   }
 
+  //--------------------------------------------------
+  // Helper functions.
+  //--------------------------------------------------
+
   function stopProcess(service, process, callback) {
     var count = 0;
     var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
diff --git a/dom/wifi/WifiNetUtil.jsm b/dom/wifi/WifiNetUtil.jsm
index c672183e4ce34a9baeb0cd3442d6335151c252a1..d79542ceda6f67de935b5117aa79ca33e9761be8 100644
--- a/dom/wifi/WifiNetUtil.jsm
+++ b/dom/wifi/WifiNetUtil.jsm
@@ -11,16 +11,23 @@ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
 
-XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
-                                   "@mozilla.org/network/manager;1",
-                                   "nsINetworkManager");
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkService",
+                                   "@mozilla.org/network/service;1",
+                                   "nsINetworkService");
 
 this.EXPORTED_SYMBOLS = ["WifiNetUtil"];
 
 const DHCP_PROP = "init.svc.dhcpcd";
 const DHCP      = "dhcpcd";
+const DEBUG     = false;
 
 this.WifiNetUtil = function(controlMessage) {
+  function debug(msg) {
+    if (DEBUG) {
+      dump('-------------- NetUtil: ' + msg);
+    }
+  }
+
   var util = {};
 
   util.configureInterface = function(cfg, callback) {
@@ -67,14 +74,14 @@ this.WifiNetUtil = function(controlMessage) {
     });
   };
 
-  util.startDhcpServer = function (range, callback) {
-    gNetworkManager.setDhcpServer(true, range, function (error) {
+  util.startDhcpServer = function (config, callback) {
+    gNetworkService.setDhcpServer(true, config, function (error) {
       callback(!error);
     });
   };
 
   util.stopDhcpServer = function (callback) {
-    gNetworkManager.setDhcpServer(false, null, function (error) {
+    gNetworkService.setDhcpServer(false, null, function (error) {
       callback(!error);
     });
   };
@@ -135,6 +142,7 @@ this.WifiNetUtil = function(controlMessage) {
 
   util.runIpConfig = function (name, data, callback) {
     if (!data) {
+      debug("IP config failed to run");
       callback({ info: data });
       return;
     }
@@ -142,16 +150,19 @@ this.WifiNetUtil = function(controlMessage) {
     setProperty("net." + name + ".dns1", ipToString(data.dns1),
                 function(ok) {
       if (!ok) {
+        debug("Unable to set net.<ifname>.dns1");
         return;
       }
       setProperty("net." + name + ".dns2", ipToString(data.dns2),
                   function(ok) {
         if (!ok) {
+          debug("Unable to set net.<ifname>.dns2");
           return;
         }
         setProperty("net." + name + ".gw", ipToString(data.gateway),
                     function(ok) {
           if (!ok) {
+            debug("Unable to set net.<ifname>.gw");
             return;
           }
           callback({ info: data });
diff --git a/dom/wifi/WifiP2pManager.jsm b/dom/wifi/WifiP2pManager.jsm
new file mode 100644
index 0000000000000000000000000000000000000000..93499cfacb12622204741e818ae068e619579877
--- /dev/null
+++ b/dom/wifi/WifiP2pManager.jsm
@@ -0,0 +1,1597 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/StateMachine.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/systemlibs.js");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gSysMsgr",
+                                   "@mozilla.org/system-message-internal;1",
+                                   "nsISystemMessagesInternal");
+
+XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
+                                   "@mozilla.org/network/manager;1",
+                                   "nsINetworkManager");
+
+const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
+
+this.EXPORTED_SYMBOLS = ["WifiP2pManager"];
+
+const EVENT_IGNORED                      = -1;
+const EVENT_UNKNOWN                      = -2;
+
+// Events from supplicant for p2p.
+const EVENT_P2P_DEVICE_FOUND             = 0;
+const EVENT_P2P_DEVICE_LOST              = 1;
+const EVENT_P2P_GROUP_STARTED            = 2;
+const EVENT_P2P_GROUP_REMOVED            = 3;
+const EVENT_P2P_PROV_DISC_PBC_REQ        = 4;
+const EVENT_P2P_PROV_DISC_PBC_RESP       = 5;
+const EVENT_P2P_PROV_DISC_SHOW_PIN       = 6;
+const EVENT_P2P_PROV_DISC_ENTER_PIN      = 7;
+const EVENT_P2P_GO_NEG_REQUEST           = 8;
+const EVENT_P2P_GO_NEG_SUCCESS           = 9;
+const EVENT_P2P_GO_NEG_FAILURE           = 10;
+const EVENT_P2P_GROUP_FORMATION_SUCCESS  = 11;
+const EVENT_P2P_GROUP_FORMATION_FAILURE  = 12;
+const EVENT_P2P_FIND_STOPPED             = 13;
+const EVENT_P2P_INVITATION_RESULT        = 14;
+const EVENT_P2P_INVITATION_RECEIVED      = 15;
+const EVENT_P2P_PROV_DISC_FAILURE        = 16;
+
+// Events from supplicant but not p2p specific.
+const EVENT_AP_STA_DISCONNECTED          = 100;
+const EVENT_AP_STA_CONNECTED             = 101;
+
+// Events from DOM.
+const EVENT_P2P_SET_PAIRING_CONFIRMATION = 1000;
+const EVENT_P2P_CMD_CONNECT              = 1001;
+const EVENT_P2P_CMD_DISCONNECT           = 1002;
+const EVENT_P2P_CMD_ENABLE               = 1003;
+const EVENT_P2P_CMD_DISABLE              = 1004;
+const EVENT_P2P_CMD_ENABLE_SCAN          = 1005;
+const EVENT_P2P_CMD_DISABLE_SCAN         = 1006;
+const EVENT_P2P_CMD_BLOCK_SCAN           = 1007;
+const EVENT_P2P_CMD_UNBLOCK_SCAN         = 1008;
+
+// Internal events.
+const EVENT_TIMEOUT_PAIRING_CONFIRMATION = 10000;
+const EVENT_TIMEOUT_NEG_REQ              = 10001;
+const EVENT_TIMEOUT_CONNECTING           = 10002;
+const EVENT_P2P_ENABLE_SUCCESS           = 10003;
+const EVENT_P2P_ENABLE_FAILED            = 10004;
+const EVENT_P2P_DISABLE_SUCCESS          = 10005;
+
+// WPS method string.
+const WPS_METHOD_PBC     = "pbc";
+const WPS_METHOD_DISPLAY = "display";
+const WPS_METHOD_KEYPAD  = "keypad";
+
+// Role string.
+const P2P_ROLE_GO     = "GO";
+const P2P_ROLE_CLIENT = "client";
+
+// System message for pairing request.
+const PAIRING_REQUEST_SYS_MSG = "wifip2p-pairing-request";
+
+// Configuration.
+const P2P_INTERFACE_NAME = "p2p0";
+const DEFAULT_GO_INTENT = 15;
+const DEFAULT_P2P_DEVICE_NAME = "FirefoxPhone";
+const P2P_SCAN_TIMEOUT_SEC = 120;
+const DEFAULT_P2P_WPS_METHODS = "virtual_push_button physical_display keypad"; // For wpa_supplicant.
+const DEFAULT_P2P_DEVICE_TYPE = "10-0050F204-5"; // For wpa_supplicant.
+
+const GO_NETWORK_INTERFACE = {
+  ip:         "192.168.2.1",
+  netmask:    "255.255.255.0", // Should be consistent with |maskLenth|.
+  maskLength: 24,              // Should be consistent with |netmask|.
+  broadcast:  "192.168.2.255",
+  gateway:    "192.168.2.1",
+  dns1:       "0.0.0.0",
+  dns2:       "0.0.0.0",
+  dhcpServer: "192.168.2.1"
+};
+
+const GO_DHCP_SERVER_IP_RANGE = {
+  startIp: "192.168.2.10",
+  endIp:   "192.168.2.30"
+};
+
+let gDebug = false;
+
+// Device Capability bitmap
+const DEVICE_CAPAB_SERVICE_DISCOVERY         = 1;
+const DEVICE_CAPAB_CLIENT_DISCOVERABILITY    = 1<<1;
+const DEVICE_CAPAB_CONCURRENT_OPER           = 1<<2;
+const DEVICE_CAPAB_INFRA_MANAGED             = 1<<3;
+const DEVICE_CAPAB_DEVICE_LIMIT              = 1<<4;
+const DEVICE_CAPAB_INVITATION_PROCEDURE      = 1<<5;
+
+// Group Capability bitmap
+const GROUP_CAPAB_GROUP_OWNER                = 1;
+const GROUP_CAPAB_PERSISTENT_GROUP           = 1<<1;
+const GROUP_CAPAB_GROUP_LIMIT                = 1<<2;
+const GROUP_CAPAB_INTRA_BSS_DIST             = 1<<3;
+const GROUP_CAPAB_CROSS_CONN                 = 1<<4;
+const GROUP_CAPAB_PERSISTENT_RECONN          = 1<<5;
+const GROUP_CAPAB_GROUP_FORMATION            = 1<<6;
+
+// Constants defined in wpa_supplicants.
+const DEV_PW_REGISTRAR_SPECIFIED = 5;
+const DEV_PW_USER_SPECIFIED      = 1;
+const DEV_PW_PUSHBUTTON          = 4;
+
+this.WifiP2pManager = function (aP2pCommand, aNetUtil) {
+  function debug(aMsg) {
+    if (gDebug) {
+      dump('-------------- WifiP2pManager: ' + aMsg);
+    }
+  }
+
+  let manager = {};
+
+  let _stateMachine = P2pStateMachine(aP2pCommand, aNetUtil);
+
+  // Set debug flag to true or false.
+  //
+  // @param aDebug Boolean to indicate enabling or disabling the debug flag.
+  manager.setDebug = function(aDebug) {
+    gDebug = aDebug;
+  };
+
+  // Set observer of observing internal state machine events.
+  //
+  // @param aObserver Used to notify WifiWorker what's happening
+  //        in the internal p2p state machine.
+  manager.setObserver = function(aObserver) {
+    _stateMachine.setObserver(aObserver);
+  };
+
+  // Handle wpa_supplicant events.
+  //
+  // @param aEventString string from wpa_supplicant.
+  manager.handleEvent = function(aEventString) {
+    let event = parseEventString(aEventString);
+    if (EVENT_UNKNOWN === event.id || EVENT_IGNORED === event.id) {
+      debug('Unknow or ignored event: ' + aEventString);
+      return false;
+    }
+    return _stateMachine.sendEvent(event);
+  };
+
+  // Set the confirmation of pairing request.
+  //
+  // @param aResult Object of confirmation result which contains:
+  //    .accepted: user granted.
+  //    .pin: pin code which is displaying or input by user.
+  //    .wpsMethod: string of "pbc" or "display" or "keypad".
+  manager.setPairingConfirmation = function(aResult) {
+    let event = {
+      id: EVENT_P2P_SET_PAIRING_CONFIRMATION,
+      info: {
+        accepted: aResult.accepted,
+        pin: aResult.pin
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Connect to a known peer.
+  //
+  // @param aAddress MAC address of the peer to connect.
+  // @param aWpsMethod String of "pbc" or "display" or "keypad".
+  // @param aGoIntent Number from 0 to 15.
+  // @param aCallback Callback |true| on attempting to connect.
+  //                          |false| on failed to connect.
+  manager.connect = function(aAddress, aWpsMethod, aGoIntent, aCallback) {
+    let event = {
+      id: EVENT_P2P_CMD_CONNECT,
+      info: {
+        wpsMethod: aWpsMethod,
+        address: aAddress,
+        goIntent: aGoIntent,
+        onDoConnect: aCallback
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Disconnect with a known peer.
+  //
+  // @param aAddress The address the user desires to disconect.
+  // @param aCallback Callback |true| on "attempting" to disconnect.
+  //                           |false| on failed to disconnect.
+  manager.disconnect = function(aAddress, aCallback) {
+    let event = {
+      id: EVENT_P2P_CMD_DISCONNECT,
+      info: {
+        address: aAddress,
+        onDoDisconnect: aCallback
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Enable/disable wifi p2p.
+  //
+  // @param aEnabled |true| to enable, |false| to disable.
+  // @param aCallbacks object for callbacks:
+  //   .onEnabled
+  //   .onDisabled
+  //   .onSupplicantConnected
+  manager.setEnabled = function(aEnabled, aCallbacks) {
+    let event = {
+      id: (aEnabled ? EVENT_P2P_CMD_ENABLE : EVENT_P2P_CMD_DISABLE),
+      info: {
+        onEnabled: aCallbacks.onEnabled,
+        onDisabled: aCallbacks.onDisabled,
+        onSupplicantConnected: aCallbacks.onSupplicantConnected
+      }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Enable/disable the wifi p2p scan.
+  //
+  // @param aEnabled |true| to enable scan, |false| to disable scan.
+  // @param aCallback Callback |true| on success to enable/disable scan.
+  //                           |false| on failed to enable/disable scan.
+  manager.setScanEnabled = function(aEnabled, aCallback) {
+    let event = {
+      id: (aEnabled ? EVENT_P2P_CMD_ENABLE_SCAN : EVENT_P2P_CMD_DISABLE_SCAN),
+      info: { callback: aCallback }
+    };
+    _stateMachine.sendEvent(event);
+  };
+
+  // Block wifi p2p scan.
+  manager.blockScan = function() {
+    _stateMachine.sendEvent({ id: EVENT_P2P_CMD_BLOCK_SCAN });
+  };
+
+  // Un-block and do the pending scan if any.
+  manager.unblockScan = function() {
+    _stateMachine.sendEvent({ id: EVENT_P2P_CMD_UNBLOCK_SCAN });
+  };
+
+  // Set the p2p device name.
+  manager.setDeviceName = function(newDeivceName, callback) {
+    aP2pCommand.setDeviceName(newDeivceName, callback);
+  };
+
+  // Parse wps_supplicant event string.
+  //
+  // @param aEventString The raw event string from wpa_supplicant.
+  //
+  // @return Object:
+  //   .id: a number to represent an event.
+  //   .info: the additional information carried by this event string.
+  function parseEventString(aEventString) {
+    if (isIgnoredEvent(aEventString)) {
+      return { id: EVENT_IGNORED };
+    }
+
+    let match = RegExp("p2p_dev_addr=([0-9a-fA-F:]+) " +
+                       "pri_dev_type=([0-9a-zA-Z-]+) " +
+                       "name='(.*)' " +
+                       "config_methods=0x([0-9a-fA-F]+) " +
+                       "dev_capab=0x([0-9a-fA-F]+) " +
+                       "group_capab=0x([0-9a-fA-F]+) ").exec(aEventString + ' ');
+
+    let tokens = aEventString.split(" ");
+
+    let id = EVENT_UNKNOWN;
+
+    // general info.
+    let info = {};
+
+    if (match) {
+      info = {
+        address:   match[1] ? match[1] : null,
+        type:      match[2] ? match[2] : null,
+        name:      match[3] ? match[3] : null,
+        wpsFlag:   match[4] ? parseInt(match[4], 16) : null,
+        devFlag:   match[5] ? parseInt(match[5], 16) : null,
+        groupFlag: match[6] ? parseInt(match[6], 16) : null
+      };
+    }
+
+    if (0 === aEventString.indexOf("P2P-DEVICE-FOUND")) {
+      id = EVENT_P2P_DEVICE_FOUND;
+      info.wpsCapabilities = wpsFlagToCapabilities(info.wpsFlag);
+      info.isGroupOwner = isPeerGroupOwner(info.groupFlag);
+    } else if (0 === aEventString.indexOf("P2P-DEVICE-LOST")) {
+      // e.g. "P2P-DEVICE-LOST p2p_dev_addr=5e:0a:5b:15:1f:80".
+      id = EVENT_P2P_DEVICE_LOST;
+      info.address = /p2p_dev_addr=([0-9a-f:]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-GROUP-STARTED")) {
+      // e.g. "P2P-GROUP-STARTED wlan0-p2p-0 GO ssid="DIRECT-3F Testing
+      //       passphrase="12345678" go_dev_addr=02:40:61:c2:f3:b7 [PERSISTENT]".
+
+      id = EVENT_P2P_GROUP_STARTED;
+      let groupMatch = RegExp('ssid="(.*)" ' +
+                              'freq=([0-9]*) ' +
+                              '(passphrase|psk)=([^ ]+) ' +
+                              'go_dev_addr=([0-9a-f:]+)').exec(aEventString);
+      info.ssid = groupMatch[1];
+      info.freq = groupMatch[2];
+      if ('passphrase' === groupMatch[3]) {
+        let s = groupMatch[4]; // e.g. "G7jHkkz9".
+        info.passphrase = s.substring(1, s.length-1); // Trim the double quote.
+      } else { // psk
+        info.psk = groupMatch[4];
+      }
+      info.goAddress = groupMatch[5];
+      info.ifname = tokens[1];
+      info.role = tokens[2];
+    } else if (0 === aEventString.indexOf("P2P-GROUP-REMOVED")) {
+      id = EVENT_P2P_GROUP_REMOVED;
+      // e.g. "P2P-GROUP-REMOVED wlan0-p2p-0 GO".
+      info.ifname = tokens[1];
+      info.role = tokens[2];
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-REQ")) {
+      id = EVENT_P2P_PROV_DISC_PBC_REQ;
+      info.wpsMethod = WPS_METHOD_PBC;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-PBC-RESP")) {
+      id = EVENT_P2P_PROV_DISC_PBC_RESP;
+      // The address is different from the general pattern.
+      info.address = aEventString.split(" ")[1];
+      info.wpsMethod = WPS_METHOD_PBC;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-SHOW-PIN")) {
+      id = EVENT_P2P_PROV_DISC_SHOW_PIN;
+      // Obtain peer address and pin from tokens.
+      info.address = tokens[1];
+      info.pin     = tokens[2];
+      info.wpsMethod = WPS_METHOD_DISPLAY;
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-ENTER-PIN")) {
+      id = EVENT_P2P_PROV_DISC_ENTER_PIN;
+      // Obtain peer address from tokens.
+      info.address = tokens[1];
+      info.wpsMethod = WPS_METHOD_KEYPAD;
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-REQUEST")) {
+      id = EVENT_P2P_GO_NEG_REQUEST;
+      info.address = tokens[1];
+      switch (parseInt(tokens[2].split("=")[1], 10)) {
+        case DEV_PW_REGISTRAR_SPECIFIED: // (5) Peer is display.
+          info.wpsMethod = WPS_METHOD_KEYPAD;
+          break;
+        case DEV_PW_USER_SPECIFIED: // (1) Peer is keypad.
+          info.wpsMethod = WPS_METHOD_DISPLAY;
+          break;
+        case DEV_PW_PUSHBUTTON: // (4) Peer is pbc.
+          info.wpsMethod = WPS_METHOD_PBC;
+          break;
+        default:
+          debug('Unknown wps method from event P2P-GO-NEG-REQUEST');
+          break;
+      }
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-SUCCESS")) {
+      id = EVENT_P2P_GO_NEG_SUCCESS;
+    } else if (0 === aEventString.indexOf("P2P-GO-NEG-FAILURE")) {
+      id = EVENT_P2P_GO_NEG_FAILURE;
+    } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-FAILURE")) {
+      id = EVENT_P2P_GROUP_FORMATION_FAILURE;
+    } else if (0 === aEventString.indexOf("P2P-GROUP-FORMATION-SUCCESS")) {
+      id = EVENT_P2P_GROUP_FORMATION_SUCCESS;
+    } else if (0 === aEventString.indexOf("P2P-FIND-STOPPED")) {
+      id = EVENT_P2P_FIND_STOPPED;
+    } else if (0 === aEventString.indexOf("P2P-INVITATION-RESULT")) {
+      id = EVENT_P2P_INVITATION_RESULT;
+      info.status = /status=([0-9]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-INVITATION-RECEIVED")) {
+      // e.g. "P2P-INVITATION-RECEIVED sa=32:85:a9:da:e6:1f persistent=7".
+      id = EVENT_P2P_INVITATION_RECEIVED;
+      info.address = /sa=([0-9a-f:]+)/.exec(aEventString)[1];
+      info.netId = /persistent=([0-9]+)/.exec(aEventString)[1];
+    } else if (0 === aEventString.indexOf("P2P-PROV-DISC-FAILURE")) {
+      id = EVENT_P2P_PROV_DISC_FAILURE;
+    } else {
+      // Not P2P event but we do receive it. Try to recognize it.
+      if (0 === aEventString.indexOf("AP-STA-DISCONNECTED")) {
+        id = EVENT_AP_STA_DISCONNECTED;
+        info.address = tokens[1];
+      } else if (0 === aEventString.indexOf("AP-STA-CONNECTED")) {
+        id = EVENT_AP_STA_CONNECTED;
+        info.address = tokens[1];
+      } else {
+        // Neither P2P event nor recognized supplicant event.
+        debug('Unknwon event string: ' + aEventString);
+      }
+    }
+
+    let event = {id: id, info: info};
+    debug('Event parsing result: ' + aEventString + ": " + JSON.stringify(event));
+
+    return event;
+  }
+
+  function isIgnoredEvent(aEventString) {
+    const IGNORED_EVENTS = [
+      "CTRL-EVENT-BSS-ADDED",
+      "CTRL-EVENT-BSS-REMOVED",
+      "CTRL-EVENT-SCAN-RESULTS",
+      "CTRL-EVENT-STATE-CHANGE",
+      "WPS-AP-AVAILABLE",
+      "WPS-ENROLLEE-SEEN"
+    ];
+    for(let i = 0; i < IGNORED_EVENTS.length; i++) {
+      if (0 === aEventString.indexOf(IGNORED_EVENTS[i])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  function isPeerGroupOwner(aGroupFlag) {
+    return (aGroupFlag & GROUP_CAPAB_GROUP_OWNER) !== 0;
+  }
+
+  // Convert flag to a wps capability array.
+  //
+  // @param aWpsFlag Number that represents the wps capabilities.
+  // @return Array of WPS flag.
+  function wpsFlagToCapabilities(aWpsFlag) {
+    let wpsCapabilities = [];
+    if (aWpsFlag & 0x8) {
+      wpsCapabilities.push(WPS_METHOD_DISPLAY);
+    }
+    if (aWpsFlag & 0x80) {
+      wpsCapabilities.push(WPS_METHOD_PBC);
+    }
+    if (aWpsFlag & 0x100) {
+      wpsCapabilities.push(WPS_METHOD_KEYPAD);
+    }
+    return wpsCapabilities;
+  }
+
+  _stateMachine.start();
+  return manager;
+};
+
+function P2pStateMachine(aP2pCommand, aNetUtil) {
+  function debug(aMsg) {
+    if (gDebug) {
+      dump('-------------- WifiP2pStateMachine: ' + aMsg);
+    }
+  }
+
+  let p2pSm = {};  // The state machine to return.
+
+  let _sm = StateMachine('WIFIP2P'); // The general purpose state machine.
+
+  // Information we need to keep track across states.
+  let _observer;
+
+  let _onEnabled;
+  let _onDisabled;
+  let _onSupplicantConnected;
+  let _savedConfig = {}; // Configuration used to do P2P_CONNECT.
+  let _groupInfo = {};   // The information of the group we have formed.
+  let _removedGroupInfo = {}; // Used to store the group info we are going to remove.
+
+  let _scanBlocked = false;
+  let _scanPostponded = false;
+
+  let _localDevice = {
+    address: "",
+    deviceName: DEFAULT_P2P_DEVICE_NAME + "_" + libcutils.property_get("ro.build.product"),
+    wpsCapabilities: [WPS_METHOD_PBC, WPS_METHOD_KEYPAD, WPS_METHOD_DISPLAY]
+  };
+
+  let _p2pNetworkInterface = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInterface]),
+
+    state: Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED,
+    type: Ci.nsINetworkInterface.NETWORK_TYPE_WIFI_P2P,
+    name: P2P_INTERFACE_NAME,
+    ip: null,
+    netmask: null,
+    broadcast: null,
+    dns1: null,
+    dns2: null,
+    gateway: null,
+    httpProxyHost: null,
+    httpProxyPort: null,
+
+    // help
+    registered: false,
+  };
+
+  //---------------------------------------------------------
+  // State machine APIs.
+  //---------------------------------------------------------
+
+  // Register the observer which is implemented in WifiP2pWorkerObserver.jsm.
+  //
+  // @param aObserver:
+  //   .onEnabled
+  //   .onDisbaled
+  //   .onPeerFound
+  //   .onPeerLost
+  //   .onConnecting
+  //   .onConnected
+  //   .onDisconnected
+  //   .onLocalDeviceChanged
+  p2pSm.setObserver = function(aObserver) {
+    _observer = aObserver;
+  };
+
+  p2pSm.start = function() {
+    _sm.start(stateDisabled);
+  };
+
+  p2pSm.sendEvent = function(aEvent) {
+    let willBeHandled = isInP2pManagedState(_sm.getCurrentState());
+    _sm.sendEvent(aEvent);
+    return willBeHandled;
+  };
+
+  // Initialize internal state machine _sm.
+  _sm.setDefaultEventHandler(handleEventCommon);
+
+  //----------------------------------------------------------
+  // State definition.
+  //----------------------------------------------------------
+
+  // The initial state.
+  var stateDisabled = _sm.makeState("DISABLED", {
+    enter: function() {
+      _onEnabled = null;
+      _onSupplicantConnected = null;
+      _savedConfig = null;
+      _groupInfo = null;
+      _removedGroupInfo = null;
+      _scanBlocked = false;
+      _scanPostponded = false;
+
+      unregisterP2pNetworkInteface();
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_CMD_ENABLE:
+          _onEnabled = aEvent.info.onEnabled;
+          _onSupplicantConnected = aEvent.info.onSupplicantConnected;
+          _sm.gotoState(stateEnabling);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    }
+  });
+
+  // The state where we are trying to enable wifi p2p.
+  var stateEnabling = _sm.makeState("ENABLING", {
+    enter: function() {
+
+      function onFailure()
+      {
+        _onEnabled(false);
+        _sm.gotoState(stateDisabled);
+      }
+
+      function onSuccess()
+      {
+        _onEnabled(true);
+        _sm.gotoState(stateInactive);
+      }
+
+      _sm.pause();
+
+      // Step 1: Connect to p2p0.
+      aP2pCommand.connectToSupplicant(function (status) {
+        let detail;
+
+        if (0 !== status) {
+          debug('Failed to connect to p2p0');
+          onFailure();
+          return;
+        }
+
+        debug('wpa_supplicant p2p0 connected!');
+        _onSupplicantConnected();
+
+        // Step 2: Get MAC address.
+        if (!_localDevice.address) {
+          aP2pCommand.getMacAddress(function (address) {
+            if (!address) {
+              debug('Failed to get MAC address....');
+              onFailure();
+              return;
+            }
+            debug('Got mac address: ' + address);
+            _localDevice.address = address;
+            _observer.onLocalDeviceChanged(_localDevice);
+          });
+        }
+
+        // Step 3: Enable p2p with the device name and wps methods.
+        detail = { deviceName: _localDevice.deviceName,
+                   deviceType: libcutils.property_get("ro.moz.wifi.p2p_device_type") || DEFAULT_P2P_DEVICE_TYPE,
+                   wpsMethods: libcutils.property_get("ro.moz.wifi.p2p_wps_methods") || DEFAULT_P2P_WPS_METHODS };
+
+        aP2pCommand.p2pEnable(detail, function (success) {
+          if (!success) {
+            debug('Failed to enable p2p');
+            onFailure();
+            return;
+          }
+
+          debug('P2P is enabled! Enabling net interface...');
+
+          // Step 4: Enable p2p0 net interface. wpa_supplicant may have
+          //         already done it for us.
+          aNetUtil.enableInterface(P2P_INTERFACE_NAME, function (success) {
+            onSuccess();
+          });
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      // We won't receive any event since all of them will be blocked.
+      return true;
+    }
+  });
+
+  // The state just after enabling wifi direct.
+  var stateInactive = _sm.makeState("INACTIVE", {
+    enter: function() {
+      registerP2pNetworkInteface();
+
+      if (_sm.getPreviousState() !== stateEnabling) {
+        _observer.onDisconnected(_savedConfig);
+      }
+
+      _savedConfig = null; // Used to connect p2p peer.
+      _groupInfo   = null; // The information of the formed group.
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        // Receiving the following 3 states implies someone is trying to
+        // connect to me.
+        case EVENT_P2P_PROV_DISC_PBC_REQ:
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            name:      aEvent.info.name,
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            goIntent:  DEFAULT_GO_INTENT,
+            pin:       aEvent.info.pin // EVENT_P2P_PROV_DISC_SHOW_PIN only.
+          };
+
+          _sm.gotoState(stateWaitingForConfirmation);
+          break;
+
+        // Connect to a peer.
+        case EVENT_P2P_CMD_CONNECT:
+          debug('Trying to connect to peer: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            goIntent:  aEvent.info.goIntent
+          };
+
+          _sm.gotoState(stateProvisionDiscovery);
+          aEvent.info.onDoConnect(true);
+          break;
+
+        case EVENT_P2P_INVITATION_RECEIVED:
+          _savedConfig = {
+            address: aEvent.info.address,
+            wpsMethod: WPS_METHOD_PBC,
+            goIntent: DEFAULT_GO_INTENT,
+            netId: aEvent.info.netId
+          };
+          _sm.gotoState(stateWaitingForInvitationConfirmation);
+          break;
+
+        case EVENT_P2P_GROUP_STARTED:
+          // Most likely the peer just reinvoked a peristen group and succeeeded.
+
+          _savedConfig = { address: aEvent.info.goAddress };
+
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_AP_STA_DISCONNECTED:
+          // We will hit this case when we used to be a group owner and
+          // requested to remove the group we owned.
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+  });
+
+  // Waiting for user's confirmation.
+  var stateWaitingForConfirmation = _sm.makeState("WAITING_FOR_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected this request');
+            _sm.gotoState(stateInactive); // Reset to inactive state.
+            break;
+          }
+
+          debug('User accepted this request');
+
+          // The only information we may have to grab from user.
+          _savedConfig.pin = aEvent.info.pin;
+
+          // The case that user requested to form a group ealier on.
+          // Just go to connecting state and do p2p_connect.
+          if (_sm.getPreviousState() === stateProvisionDiscovery) {
+            _sm.gotoState(stateConnecting);
+            break;
+          }
+
+          // Otherwise, wait for EVENT_P2P_GO_NEG_REQUEST.
+          _sm.gotoState(stateWaitingForNegReq);
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('Confirmation timeout!');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GO_NEG_REQUEST:
+          _sm.deferEvent(aEvent);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateWaitingForNegReq = _sm.makeState("WAITING_FOR_NEG_REQ", {
+    timeoutTimer: null,
+
+    enter: function() {
+      debug('Wait for EVENT_P2P_GO_NEG_REQUEST');
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_NEG_REQ);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GO_NEG_REQUEST:
+          if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+            debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ", " + _savedConfig.wpsMetho);
+          }
+          _sm.gotoState(stateConnecting);
+          break;
+
+        case EVENT_TIMEOUT_NEG_REQ:
+          debug("Waiting for NEG-REQ timeout");
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  // Waiting for user's confirmation for invitation.
+  var stateWaitingForInvitationConfirmation = _sm.makeState("WAITING_FOR_INV_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected this request');
+            _sm.gotoState(stateInactive); // Reset to inactive state.
+            break;
+          }
+
+          debug('User accepted this request');
+          _sm.pause();
+          aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function (gc) {
+            let isPeeGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+            _sm.gotoState(isPeeGroupOwner ? stateGroupAdding : stateReinvoking);
+          });
+
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('Confirmation timeout!');
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateGroupAdding = _sm.makeState("GROUP_ADDING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      _observer.onConnecting(_savedConfig);
+
+      _sm.pause();
+      aP2pCommand.p2pGroupAdd(_savedConfig.netId, function (success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          return;
+        }
+        // Waiting for EVENT_P2P_GROUP_STARTED.
+        self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+        _sm.resume();
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateReinvoking = _sm.makeState("REINVOKING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      _observer.onConnecting(_savedConfig);
+      _sm.pause();
+      aP2pCommand.p2pReinvoke(_savedConfig.netId, _savedConfig.address, function(success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          return;
+        }
+        // Waiting for EVENT_P2P_GROUP_STARTED.
+        self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+        _sm.resume();
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function(success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+    }
+  });
+
+  var stateProvisionDiscovery = _sm.makeState("PROVISION_DISCOVERY", {
+    enter: function() {
+      function onDiscoveryCommandSent(success) {
+        if (!success) {
+          _sm.gotoState(stateInactive);
+          debug('Failed to send p2p_prov_disc. Go back to inactive state.');
+          return;
+        }
+
+        debug('p2p_prov_disc has been sent.');
+
+        _sm.resume();
+        // Waiting for EVENT_P2P_PROV_DISC_PBC_RESP or
+        //             EVENT_P2P_PROV_DISC_SHOW_PIN or
+        //             EVENT_P2P_PROV_DISC_ENTER_PIN.
+      }
+
+      _sm.pause();
+      aP2pCommand.p2pProvDiscovery(_savedConfig.address,
+                                   toPeerWpsMethod(_savedConfig.wpsMethod),
+                                   onDiscoveryCommandSent);
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_PROV_DISC_PBC_RESP:
+          _sm.gotoState(stateConnecting); // No need for local user grant.
+          break;
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          if (aEvent.info.wpsMethod !== _savedConfig.wpsMethod) {
+            debug('Unmatched wps method: ' + aEvent.info.wpsMethod + ":" + _savedConfig.wpsMethod);
+          }
+          if (EVENT_P2P_PROV_DISC_SHOW_PIN === aEvent.id) {
+            _savedConfig.pin = aEvent.info.pin;
+          }
+          _sm.gotoState(stateWaitingForConfirmation);
+          break;
+
+        case EVENT_P2P_PROV_DISC_FAILURE:
+          _sm.gotoState(stateInactive);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    }
+  });
+
+  // We are going to connect to the peer.
+  // |_savedConfig| is supposed to have been filled properly.
+  var stateConnecting = _sm.makeState("CONNECTING", {
+    timeoutTimer: null,
+
+    enter: function() {
+      let self = this;
+
+      if (null === _savedConfig.goIntent) {
+        _savedConfig.goIntent = DEFAULT_GO_INTENT;
+      }
+
+      _observer.onConnecting(_savedConfig);
+
+      let wpsMethodWithPin;
+      if (WPS_METHOD_KEYPAD === _savedConfig.wpsMethod ||
+          WPS_METHOD_DISPLAY === _savedConfig.wpsMethod) {
+        // e.g. '12345678 display or '12345678 keypad'.
+        wpsMethodWithPin = (_savedConfig.pin + ' ' + _savedConfig.wpsMethod);
+      } else {
+        // e.g. 'pbc'.
+        wpsMethodWithPin = _savedConfig.wpsMethod;
+      }
+
+      _sm.pause();
+
+      aP2pCommand.p2pGetGroupCapab(_savedConfig.address, function(gc) {
+        debug('group capabilities of ' + _savedConfig.address + ': ' + gc);
+
+        let isPeerGroupOwner = gc & GROUP_CAPAB_GROUP_OWNER;
+        let config = { address:           _savedConfig.address,
+                       wpsMethodWithPin:  wpsMethodWithPin,
+                       goIntent:          _savedConfig.goIntent,
+                       joinExistingGroup: isPeerGroupOwner };
+
+        aP2pCommand.p2pConnect(config, function (success) {
+          if (!success) {
+            debug('Failed to send p2p_connect');
+            _sm.gotoState(stateInactive);
+            return;
+          }
+          debug('Waiting for EVENT_P2P_GROUP_STARTED.');
+          self.timeoutTimer = initTimeoutTimer(60000, EVENT_TIMEOUT_CONNECTING);
+          _sm.resume();
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_GROUP_STARTED:
+          _sm.pause();
+          handleGroupStarted(aEvent.info, function (success) {
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_GO_NEG_FAILURE:
+          debug('Negotiation failure. Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_TIMEOUT_CONNECTING:
+          debug('Connecting timeout! Go back to inactive state');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_SUCCESS:
+        case EVENT_P2P_GO_NEG_SUCCESS:
+          break;
+
+        case EVENT_P2P_GROUP_FORMATION_FAILURE:
+          debug('Group formation failure');
+          _sm.gotoState(stateInactive);
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          debug('Received P2P-GROUP-REMOVED due to previous failed ' +
+                'handleGroupdStarted()');
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+    }
+  });
+
+  var stateConnected = _sm.makeState("CONNECTED", {
+    groupOwner: null,
+
+    enter: function() {
+      this.groupOwner = {
+        macAddress: _groupInfo.goAddress,
+        ipAddress:  _groupInfo.networkInterface.gateway,
+        passphrase: _groupInfo.passphrase,
+        ssid:       _groupInfo.ssid,
+        freq:       _groupInfo.freq,
+        isLocal:    _groupInfo.isGroupOwner
+      };
+
+      if (!_groupInfo.isGroupOwner) {
+        _observer.onConnected(this.groupOwner, _savedConfig);
+      } else {
+        // If I am a group owner, notify onConnected until EVENT_AP_STA_CONNECTED
+        // is received.
+      }
+
+      _removedGroupInfo = null;
+    },
+
+    handleEvent: function(aEvent) {
+      switch (aEvent.id) {
+        case EVENT_AP_STA_CONNECTED:
+          if (_groupInfo.isGroupOwner) {
+            _observer.onConnected(this.groupOwner, _savedConfig);
+          }
+          break;
+
+        case EVENT_P2P_GROUP_REMOVED:
+          _removedGroupInfo = {
+            role:   aEvent.info.role,
+            ifname: aEvent.info.ifname
+          };
+          _sm.gotoState(stateDisconnecting);
+          break;
+
+        case EVENT_AP_STA_DISCONNECTED:
+          debug('Client disconnected: ' + aEvent.info.address);
+
+          // Now we suppose it's the only client. Remove my group.
+          _sm.pause();
+          aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function (success) {
+            debug('Requested to remove p2p group. Wait for EVENT_P2P_GROUP_REMOVED.');
+            _sm.resume();
+          });
+          break;
+
+        case EVENT_P2P_CMD_DISCONNECT:
+          // Since we only support single connection, we can ignore
+          // the given peer address.
+          _sm.pause();
+          aP2pCommand.p2pGroupRemove(_groupInfo.ifname, function(success) {
+            aEvent.info.onDoDisconnect(true);
+            _sm.resume();
+          });
+
+          debug('Sent disconnect command. Wait for EVENT_P2P_GROUP_REMOVED.');
+          break;
+
+        case EVENT_P2P_PROV_DISC_PBC_REQ:
+        case EVENT_P2P_PROV_DISC_SHOW_PIN:
+        case EVENT_P2P_PROV_DISC_ENTER_PIN:
+          debug('Someone is trying to connect to me: ' + JSON.stringify(aEvent.info));
+
+          _savedConfig = {
+            name:      aEvent.info.name,
+            address:   aEvent.info.address,
+            wpsMethod: aEvent.info.wpsMethod,
+            pin:       aEvent.info.pin
+          };
+
+          _sm.gotoState(stateWaitingForJoiningConfirmation);
+          break;
+
+        default:
+          return false;
+      } // end of switch
+      return true;
+    }
+  });
+
+  var stateWaitingForJoiningConfirmation = _sm.makeState("WAITING_FOR_JOINING_CONFIRMATION", {
+    timeoutTimer: null,
+
+    enter: function() {
+      gSysMsgr.broadcastMessage(PAIRING_REQUEST_SYS_MSG, _savedConfig);
+      this.timeoutTimer = initTimeoutTimer(30000, EVENT_TIMEOUT_PAIRING_CONFIRMATION);
+    },
+
+    handleEvent: function (aEvent) {
+      switch (aEvent.id) {
+        case EVENT_P2P_SET_PAIRING_CONFIRMATION:
+          if (!aEvent.info.accepted) {
+            debug('User rejected invitation!');
+            _sm.gotoState(stateConnected);
+            break;
+          }
+
+          let onWpsCommandSent = function(success) {
+            _observer.onConnecting(_savedConfig);
+            _sm.gotoState(stateConnected);
+          };
+
+          _sm.pause();
+          if (WPS_METHOD_PBC === _savedConfig.wpsMethod) {
+            aP2pCommand.wpsPbc(_groupInfo.ifname, onWpsCommandSent);
+          } else {
+            let detail = { pin: _savedConfig.pin, iface: _groupInfo.ifname };
+            aP2pCommand.wpsPin(detail, onWpsCommandSent);
+          }
+          break;
+
+        case EVENT_TIMEOUT_PAIRING_CONFIRMATION:
+          debug('WAITING_FOR_JOINING_CONFIRMATION timeout!');
+          _sm.gotoState(stateConnected);
+          break;
+
+        default:
+          return false;
+      } // End of switch.
+      return true;
+    },
+
+    exit: function() {
+      this.timeoutTimer.cancel();
+      this.timeoutTimer = null;
+    }
+  });
+
+  var stateDisconnecting = _sm.makeState("DISCONNECTING", {
+    enter: function() {
+      _sm.pause();
+      handleGroupRemoved(_removedGroupInfo, function (success) {
+        if (!success) {
+          debug('Failed to handle group removed event. What can I do?');
+        }
+        _sm.gotoState(stateInactive);
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      return false; // We will not receive any event in this state.
+    }
+  });
+
+  var stateDisabling = _sm.makeState("DISABLING", {
+    enter: function() {
+      _sm.pause();
+      aNetUtil.stopDhcpServer(function (success) { // Stopping DHCP server is harmless.
+        debug('Stop DHCP server result: ' + success);
+        aP2pCommand.p2pDisable(function(success) {
+          debug('P2P function disabled');
+          aP2pCommand.closeSupplicantConnection(function (status) {
+            debug('Supplicant connection closed');
+            aNetUtil.disableInterface(P2P_INTERFACE_NAME, function (success){
+              debug('Disabled interface: ' + P2P_INTERFACE_NAME);
+              _onDisabled(true);
+              _sm.gotoState(stateDisabled);
+            });
+          });
+        });
+      });
+    },
+
+    handleEvent: function(aEvent) {
+      return false; // We will not receive any event in this state.
+    }
+  });
+
+  //----------------------------------------------------------
+  // Helper functions.
+  //----------------------------------------------------------
+
+  // Handle 'P2P_GROUP_STARTED' event. Note that this function
+  // will also do the state transitioning and error handling.
+  //
+  // @param aInfo Information carried by "P2P_GROUP_STARTED" event:
+  //   .role: P2P_ROLE_GO or P2P_ROLE_CLIENT
+  //   .ssid:
+  //   .freq:
+  //   .passphrase: Used to connect to GO for legacy device.
+  //   .goAddress:
+  //   .ifname: e.g. p2p-p2p0
+  //
+  // @param aCallback Callback function.
+  function handleGroupStarted(aInfo, aCallback) {
+    debug('handleGroupStarted: ' + JSON.stringify(aInfo));
+
+    function onSuccess()
+    {
+      _sm.gotoState(stateConnected);
+      aCallback(true);
+    }
+
+    function onFailure()
+    {
+      debug('Failed to handleGroupdStarted(). Remove the group...');
+      aP2pCommand.p2pGroupRemove(aInfo.ifname, function (success) {
+        aCallback(false);
+
+        if (success) {
+          return; // Stay in current state and wait for EVENT_P2P_GROUP_REMOVED.
+        }
+
+        debug('p2pGroupRemove command error!');
+        _sm.gotoState(stateInactive);
+      });
+    }
+
+    // Save this group information.
+    _groupInfo = aInfo;
+    _groupInfo.isGroupOwner = (P2P_ROLE_GO === aInfo.role);
+
+    if (_groupInfo.isGroupOwner) {
+      debug('Group owner. Start DHCP server');
+      let dhcpServerConfig = { ifname: aInfo.ifname,
+                               startIp: GO_DHCP_SERVER_IP_RANGE.startIp,
+                               endIp: GO_DHCP_SERVER_IP_RANGE.endIp,
+                               serverIp: GO_NETWORK_INTERFACE.ip,
+                               maskLength: GO_NETWORK_INTERFACE.maskLength };
+
+      aNetUtil.startDhcpServer(dhcpServerConfig, function (success) {
+        if (!success) {
+          debug('Failed to start DHCP server');
+          onFailure();
+          return;
+        }
+
+        // Update p2p network interface.
+        _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+        _p2pNetworkInterface.ip = GO_NETWORK_INTERFACE.ip;
+        _p2pNetworkInterface.netmask = GO_NETWORK_INTERFACE.netmask;
+        _p2pNetworkInterface.gateway = GO_NETWORK_INTERFACE.ip;
+        handleP2pNetworkInterfaceStateChanged();
+
+        _groupInfo.networkInterface = _p2pNetworkInterface;
+
+        debug('Everything is done. Happy p2p GO~');
+        onSuccess();
+      });
+
+      return;
+    }
+
+    // We are the client.
+
+    debug("Client. Request IP from DHCP server on interface: " + _groupInfo.ifname);
+
+    aNetUtil.runDhcp(aInfo.ifname, function(dhcpData) {
+      if(!dhcpData || !dhcpData.info) {
+        debug('Failed to run DHCP client');
+        onFailure();
+        return;
+      }
+
+      // Save network interface.
+      debug("DHCP request success: " + JSON.stringify(dhcpData.info));
+
+      // Update p2p network interface.
+      _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED;
+      _p2pNetworkInterface.ip = dhcpData.info.ipaddr_str;
+      _p2pNetworkInterface.netmask = dhcpData.info.mask_str;
+      _p2pNetworkInterface.broadcast = dhcpData.info.broadcast_str;
+      _p2pNetworkInterface.dns1 = dhcpData.info.dns1_str;
+      _p2pNetworkInterface.dns2 = dhcpData.info.dns2_str;
+      _p2pNetworkInterface.gateway = dhcpData.info.gateway_str;
+      handleP2pNetworkInterfaceStateChanged();
+
+      _groupInfo.networkInterface = _p2pNetworkInterface;
+
+      debug('Happy p2p client~');
+      onSuccess();
+    });
+  }
+
+  function resetP2pNetworkInterface() {
+    _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
+    _p2pNetworkInterface.ip = null;
+    _p2pNetworkInterface.netmask = null;
+    _p2pNetworkInterface.broadcast = null;
+    _p2pNetworkInterface.dns1 = null;
+    _p2pNetworkInterface.dns2 = null;
+    _p2pNetworkInterface.gateway = null;
+  }
+
+  function registerP2pNetworkInteface() {
+    if (!_p2pNetworkInterface.registered) {
+      resetP2pNetworkInterface();
+      gNetworkManager.registerNetworkInterface(_p2pNetworkInterface);
+      _p2pNetworkInterface.registered = true;
+    }
+  }
+
+  function unregisterP2pNetworkInteface() {
+    if (_p2pNetworkInterface.registered) {
+      resetP2pNetworkInterface();
+      gNetworkManager.unregisterNetworkInterface(_p2pNetworkInterface);
+      _p2pNetworkInterface.registered = false;
+    }
+  }
+
+  function handleP2pNetworkInterfaceStateChanged() {
+    Services.obs.notifyObservers(_p2pNetworkInterface,
+                                 kNetworkInterfaceStateChangedTopic,
+                                 null);
+  }
+
+  // Handle 'P2P_GROUP_STARTED' event.
+  //
+  // @param aInfo information carried by "P2P_GROUP_REMOVED" event:
+  //   .ifname
+  //   .role: "GO" or "client".
+  //
+  // @param aCallback Callback function.
+  function handleGroupRemoved(aInfo, aCallback) {
+    if (!_groupInfo) {
+      debug('No group info. Why?');
+      aCallback(true);
+      return;
+    }
+    if (_groupInfo.ifname !== aInfo.ifname ||
+        _groupInfo.role   !== aInfo.role) {
+      debug('Unmatched group info: ' + JSON.stringify(_groupInfo) +
+            ' v.s. ' + JSON.stringify(aInfo));
+    }
+
+    // Update p2p network interface.
+    _p2pNetworkInterface.state = Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED;
+    handleP2pNetworkInterfaceStateChanged();
+
+    if (P2P_ROLE_GO === aInfo.role) {
+      aNetUtil.stopDhcpServer(function(success) {
+        debug('Stop DHCP server result: ' + success);
+        aCallback(true);
+      });
+    } else {
+      aNetUtil.stopDhcp(aInfo.ifname, function() {
+        aCallback(true);
+      });
+    }
+  }
+
+  // Non state-specific event handler.
+  function handleEventCommon(aEvent) {
+    switch (aEvent.id) {
+      case EVENT_P2P_DEVICE_FOUND:
+        _observer.onPeerFound(aEvent.info);
+        break;
+
+      case EVENT_P2P_DEVICE_LOST:
+        _observer.onPeerLost(aEvent.info);
+        break;
+
+      case EVENT_P2P_CMD_DISABLE:
+        _onDisabled = aEvent.info.onDisabled;
+        _sm.gotoState(stateDisabling);
+        break;
+
+      case EVENT_P2P_CMD_ENABLE_SCAN:
+        if (_scanBlocked) {
+          _scanPostponded = true;
+          aEvent.info.callback(true);
+          break;
+        }
+        aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, aEvent.info.callback);
+        break;
+
+      case EVENT_P2P_CMD_DISABLE_SCAN:
+        aP2pCommand.p2pDisableScan(aEvent.info.callback);
+        break;
+
+      case EVENT_P2P_FIND_STOPPED:
+        break;
+
+      case EVENT_P2P_CMD_BLOCK_SCAN:
+        _scanBlocked = true;
+        aP2pCommand.p2pDisableScan(function(success) {});
+        break;
+
+      case EVENT_P2P_CMD_UNBLOCK_SCAN:
+        _scanBlocked = false;
+        if (_scanPostponded) {
+          aP2pCommand.p2pEnableScan(P2P_SCAN_TIMEOUT_SEC, function(success) {});
+        }
+        break;
+
+      case EVENT_P2P_CMD_CONNECT:
+      case EVENT_P2P_CMD_DISCONNECT:
+        debug("The current state couldn't handle connect/disconnect request. Ignore it.");
+        break;
+
+      default:
+        return false;
+    } // End of switch.
+    return true;
+  }
+
+  function isInP2pManagedState(aState) {
+    let p2pManagedStates = [stateWaitingForConfirmation,
+                            stateWaitingForNegReq,
+                            stateProvisionDiscovery,
+                            stateWaitingForInvitationConfirmation,
+                            stateGroupAdding,
+                            stateReinvoking,
+                            stateConnecting,
+                            stateConnected,
+                            stateDisconnecting];
+
+    for (let i = 0; i < p2pManagedStates.length; i++) {
+      if (aState === p2pManagedStates[i]) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  function initTimeoutTimer(aTimeoutMs, aTimeoutEvent) {
+    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    function onTimerFired() {
+      _sm.sendEvent({ id: aTimeoutEvent });
+      timer = null;
+    }
+    timer.initWithCallback(onTimerFired.bind(this), aTimeoutMs,
+                           Ci.nsITimer.TYPE_ONE_SHOT);
+    return timer;
+  }
+
+  // Converts local WPS method to peer WPS method.
+  function toPeerWpsMethod(aLocalWpsMethod) {
+    switch (aLocalWpsMethod) {
+      case WPS_METHOD_DISPLAY:
+        return WPS_METHOD_KEYPAD;
+      case WPS_METHOD_KEYPAD:
+        return WPS_METHOD_DISPLAY;
+      case WPS_METHOD_PBC:
+        return WPS_METHOD_PBC;
+      default:
+        return WPS_METHOD_PBC; // Use "push button" as the default method.
+    }
+  }
+
+  return p2pSm;
+}
+
+this.WifiP2pManager.INTERFACE_NAME = P2P_INTERFACE_NAME;
diff --git a/dom/wifi/WifiP2pWorkerObserver.jsm b/dom/wifi/WifiP2pWorkerObserver.jsm
new file mode 100644
index 0000000000000000000000000000000000000000..69d0edead4f93906f8d5c7b3d5cf28d4ad818c78
--- /dev/null
+++ b/dom/wifi/WifiP2pWorkerObserver.jsm
@@ -0,0 +1,303 @@
+/* -*- 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const CONNECTION_STATUS_DISCONNECTED  = "disconnected";
+const CONNECTION_STATUS_CONNECTING    = "connecting";
+const CONNECTION_STATUS_CONNECTED     = "connected";
+const CONNECTION_STATUS_DISCONNECTING = "disconnecting";
+
+const DEBUG = false;
+
+this.EXPORTED_SYMBOLS = ["WifiP2pWorkerObserver"];
+
+// WifiP2pWorkerObserver resides in WifiWorker to handle DOM message
+// by either 1) returning internally maintained information or
+//           2) delegating to aDomMsgResponder. It is also responsible
+// for observing events from WifiP2pManager and dispatch to DOM.
+//
+// @param aDomMsgResponder handles DOM messages, including
+//        - setScanEnabled
+//        - connect
+//        - disconnect
+//        - setPairingConfirmation
+//        The instance is actually WifiP2pManager.
+this.WifiP2pWorkerObserver = function(aDomMsgResponder) {
+  function debug(aMsg) {
+    if (DEBUG) {
+      dump('-------------- WifiP2pWorkerObserver: ' + aMsg);
+    }
+  }
+
+  // Private member variables.
+  let _localDevice;
+  let _peerList = {}; // List of P2pDevice.
+  let _domManagers = [];
+
+  // Constructor of P2pDevice. It will be exposed to DOM.
+  //
+  // @param aPeer object representing a P2P device:
+  //   .name: string for the device name.
+  //   .address: Mac address.
+  //   .isGroupOwner: boolean to indicate if this device is the group owner.
+  //   .wpsCapabilities: array of string of {"pbc", "display", "keypad"}.
+  function P2pDevice(aPeer) {
+    this.address = aPeer.address;
+    this.name = (aPeer.name ? aPeer.name : aPeer.address);
+    this.isGroupOwner = aPeer.isGroupOwner;
+    this.wpsCapabilities = aPeer.wpsCapabilities;
+    this.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+
+    // Since this object will be exposed to web, defined the exposed
+    // properties here.
+    this.__exposedProps__ = {
+      address: "r",
+      name: "r",
+      isGroupOwner: "r",
+      wpsCapabilities: "r",
+      connectionStatus: "r"
+    };
+  }
+
+  // Constructor of P2pGroupOwner.
+  //
+  // @param aGroupOwner:
+  //   .macAddress
+  //   .ipAddress
+  //   .passphrase
+  //   .ssid
+  //   .freq
+  //   .isLocal
+  function P2pGroupOwner(aGroupOwner) {
+    this.macAddress = aGroupOwner.macAddress; // The identifier to get further information.
+    this.ipAddress = aGroupOwner.ipAddress;
+    this.passphrase = aGroupOwner.passphrase;
+    this.ssid = aGroupOwner.ssid; // e.g. DIRECT-xy.
+    this.freq = aGroupOwner.freq;
+    this.isLocal = aGroupOwner.isLocal;
+
+    let detail = _peerList[aGroupOwner.macAddress];
+    if (detail) {
+      this.name = detail.name;
+      this.wpsCapabilities = detail.wpsCapabilities;
+    } else if (_localDevice.address === this.macAddress) {
+      this.name = _localDevice.name;
+      this.wpsCapabilities = _localDevice.wpsCapabilities;
+    } else {
+      debug("We don't know this group owner: " + aGroupOwner.macAddress);
+      this.name = aGroupOwner.macAddress;
+      this.wpsCapabilities = [];
+    }
+  }
+
+  function fireEvent(aMessage, aData) {
+    debug('domManager: ' + JSON.stringify(_domManagers));
+    _domManagers.forEach(function(manager) {
+      // Note: We should never have a dead message manager here because we
+      // observe our child message managers shutting down below.
+      manager.sendAsyncMessage("WifiP2pManager:" + aMessage, aData);
+    });
+  }
+
+  function addDomManager(aMsg) {
+    if (-1 === _domManagers.indexOf(aMsg.manager)) {
+      _domManagers.push(aMsg.manager);
+    }
+  }
+
+  function returnMessage(aMessage, aSuccess, aData, aMsg) {
+    let rMsg = aMessage + ":Return:" + (aSuccess ? "OK" : "NO");
+    aMsg.manager.sendAsyncMessage(rMsg,
+                                 { data: aData, rid: aMsg.rid, mid: aMsg.mid });
+  }
+
+  function handlePeerListUpdated() {
+    fireEvent("onpeerinfoupdate", {});
+  }
+
+  // Return a literal object as the constructed object.
+  return {
+    onLocalDeviceChanged: function(aDevice) {
+      _localDevice = aDevice;
+      debug('Local device updated to: ' + JSON.stringify(_localDevice));
+    },
+
+    onEnabled: function() {
+      _peerList = [];
+      fireEvent("p2pUp", {});
+    },
+
+    onDisbaled: function() {
+      fireEvent("p2pDown", {});
+    },
+
+    onPeerFound: function(aPeer) {
+      let newFoundPeer = new P2pDevice(aPeer);
+      let origianlPeer = _peerList[aPeer.address];
+      _peerList[aPeer.address] = newFoundPeer;
+      if (origianlPeer) {
+        newFoundPeer.connectionStatus = origianlPeer.connectionStatus;
+      }
+      handlePeerListUpdated();
+    },
+
+    onPeerLost: function(aPeer) {
+      let lostPeer = _peerList[aPeer.address];
+      if (!lostPeer) {
+        debug('Unknown peer lost: ' + aPeer.address);
+        return;
+      }
+      delete _peerList[aPeer.address];
+      handlePeerListUpdated();
+    },
+
+    onConnecting: function(aPeer) {
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer connecting: ' + aPeer.address);
+        peer = new P2pDevice(aPeer);
+        _peerList[aPeer.address] = peer;
+        handlePeerListUpdated();
+      }
+      peer.connectionStatus = CONNECTION_STATUS_CONNECTING;
+
+      fireEvent('onconnecting', { peer: peer });
+    },
+
+    onConnected: function(aGroupOwner, aPeer) {
+      let go = new P2pGroupOwner(aGroupOwner);
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer connected: ' + aPeer.address);
+        peer = new P2pDevice(aPeer);
+        _peerList[aPeer.address] = peer;
+        handlePeerListUpdated();
+      }
+      peer.connectionStatus = CONNECTION_STATUS_CONNECTED;
+      peer.isGroupOwner = (aPeer.address === aGroupOwner.address);
+
+      fireEvent('onconnected', { groupOwner: go, peer: peer });
+    },
+
+    onDisconnected: function(aPeer) {
+      let peer = _peerList[aPeer.address];
+      if (!peer) {
+        debug('Unknown peer disconnected: ' + aPeer.address);
+        return;
+      }
+
+      peer.connectionStatus = CONNECTION_STATUS_DISCONNECTED;
+      fireEvent('ondisconnected', { peer: peer });
+    },
+
+    getObservedDOMMessages: function() {
+      return [
+        "WifiP2pManager:getState",
+        "WifiP2pManager:getPeerList",
+        "WifiP2pManager:setScanEnabled",
+        "WifiP2pManager:connect",
+        "WifiP2pManager:disconnect",
+        "WifiP2pManager:setPairingConfirmation",
+        "WifiP2pManager:setDeviceName"
+      ];
+    },
+
+    onDOMMessage: function(aMessage) {
+      let msg = aMessage.data || {};
+      msg.manager = aMessage.target;
+
+      if ("child-process-shutdown" === aMessage.name) {
+        let i;
+        if (-1 !== (i = _domManagers.indexOf(msg.manager))) {
+          _domManagers.splice(i, 1);
+        }
+        return;
+      }
+
+      if (!aMessage.target.assertPermission("wifi-manage")) {
+        return;
+      }
+
+      switch (aMessage.name) {
+        case "WifiP2pManager:getState": // A new DOM manager is created.
+          addDomManager(msg);
+          return { peerList: _peerList, }; // Synchronous call. Simply return it.
+
+        case "WifiP2pManager:setScanEnabled":
+          {
+            let enabled = msg.data;
+
+            aDomMsgResponder.setScanEnabled(enabled, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        case "WifiP2pManager:getPeerList":
+          {
+            // Convert the object to an array.
+            let peerArray = [];
+            for (let key in _peerList) {
+              if (_peerList.hasOwnProperty(key)) {
+                peerArray.push(_peerList[key]);
+              }
+            }
+
+            returnMessage(aMessage.name, true, peerArray, msg);
+          }
+          break;
+
+        case "WifiP2pManager:connect":
+          {
+            let peer = msg.data;
+
+            let onDoConnect = function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            };
+
+            aDomMsgResponder.connect(peer.address, peer.wpsMethod,
+                                     peer.goIntent, onDoConnect);
+          }
+          break;
+
+        case "WifiP2pManager:disconnect":
+          {
+            let address = msg.data;
+
+            aDomMsgResponder.disconnect(address, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        case "WifiP2pManager:setPairingConfirmation":
+          {
+            let result = msg.data;
+            aDomMsgResponder.setPairingConfirmation(result);
+            returnMessage(aMessage.name, true, true, msg);
+          }
+          break;
+
+        case "WifiP2pManager:setDeviceName":
+          {
+            let newDeviceName = msg.data;
+            aDomMsgResponder.setDeviceName(newDeviceName, function(success) {
+              returnMessage(aMessage.name, success, (success ? true : "ERROR"), msg);
+            });
+          }
+          break;
+
+        default:
+          if (0 === aMessage.name.indexOf("WifiP2pManager:")) {
+            debug("DOM WifiP2pManager message not handled: " + aMessage.name);
+          }
+      } // End of switch.
+    }
+  };
+};
diff --git a/dom/wifi/WifiUtils.cpp b/dom/wifi/WifiUtils.cpp
index a92ba15a2e66b109bd2d226392afed3f4a45ff2c..9bd354156f6d135a08f72154f5717705b9eb5792 100644
--- a/dom/wifi/WifiUtils.cpp
+++ b/dom/wifi/WifiUtils.cpp
@@ -5,6 +5,7 @@
 #include "WifiUtils.h"
 #include <dlfcn.h>
 #include <errno.h>
+#include <cutils/properties.h>
 #include "prinit.h"
 #include "js/CharacterEncoding.h"
 #include "NetUtils.h"
@@ -34,6 +35,14 @@ GetSharedLibrary()
   return sWifiLib;
 }
 
+static bool
+GetWifiP2pSupported()
+{
+  char propP2pSupported[PROPERTY_VALUE_MAX];
+  property_get("ro.moz.wifi.p2p_supported", propP2pSupported, "0");
+  return (0 == strcmp(propP2pSupported, "1"));
+}
+
 // This is the same algorithm as in InflateUTF8StringToBuffer with Copy and
 // while ignoring invalids.
 // https://mxr.mozilla.org/mozilla-central/source/js/src/vm/CharacterEncoding.cpp#231
@@ -307,7 +316,7 @@ bool WpaSupplicant::ExecuteCommand(CommandOptions aOptions,
   } else if (aOptions.mCmd.EqualsLiteral("unload_driver")) {
     aResult.mStatus = mImpl->do_wifi_unload_driver();
   } else if (aOptions.mCmd.EqualsLiteral("start_supplicant")) {
-    aResult.mStatus = mImpl->do_wifi_start_supplicant(0);
+    aResult.mStatus = mImpl->do_wifi_start_supplicant(GetWifiP2pSupported() ? 1 : 0);
   } else if (aOptions.mCmd.EqualsLiteral("stop_supplicant")) {
     aResult.mStatus = mImpl->do_wifi_stop_supplicant(0);
   } else if (aOptions.mCmd.EqualsLiteral("connect_to_supplicant")) {
diff --git a/dom/wifi/WifiWorker.js b/dom/wifi/WifiWorker.js
index 57e58a20584e41b4812126de741c63668d021a45..824e18ddf0392c1a7c9b82cbdd7865c336243065 100644
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -13,6 +13,8 @@ Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/systemlibs.js");
 Cu.import("resource://gre/modules/WifiCommand.jsm");
 Cu.import("resource://gre/modules/WifiNetUtil.jsm");
+Cu.import("resource://gre/modules/WifiP2pManager.jsm");
+Cu.import("resource://gre/modules/WifiP2pWorkerObserver.jsm");
 
 var DEBUG = false; // set to true to show debug messages.
 
@@ -108,16 +110,30 @@ var WifiManager = (function() {
       unloadDriverEnabled: libcutils.property_get("ro.moz.wifi.unloaddriver") === "1",
       schedScanRecovery: libcutils.property_get("ro.moz.wifi.sched_scan_recover") === "false" ? false : true,
       driverDelay: libcutils.property_get("ro.moz.wifi.driverDelay"),
+      p2pSupported: libcutils.property_get("ro.moz.wifi.p2p_supported") === "1",
       ifname: libcutils.property_get("wifi.interface")
     };
   }
 
-  let {sdkVersion, unloadDriverEnabled, schedScanRecovery, driverDelay, ifname} = getStartupPrefs();
+  let {sdkVersion, unloadDriverEnabled, schedScanRecovery, driverDelay, p2pSupported, ifname} = getStartupPrefs();
 
   let wifiListener = {
     onWaitEvent: function(event, iface) {
       if (manager.ifname === iface && handleEvent(event)) {
         waitForEvent(iface);
+      } else if (p2pSupported) {
+        if (WifiP2pManager.INTERFACE_NAME === iface) {
+          // If the connection is closed, wifi.c::wifi_wait_for_event()
+          // will still return 'CTRL-EVENT-TERMINATING  - connection closed'
+          // rather than blocking. So when we see this special event string,
+          // just return immediately.
+          const TERMINATED_EVENT = 'CTRL-EVENT-TERMINATING  - connection closed';
+          if (-1 !== event.indexOf(TERMINATED_EVENT)) {
+            return;
+          }
+          p2pManager.handleEvent(event);
+          waitForEvent(iface);
+        }
       }
     },
 
@@ -135,18 +151,29 @@ var WifiManager = (function() {
   manager.schedScanRecovery = schedScanRecovery;
   manager.driverDelay = driverDelay ? parseInt(driverDelay, 10) : DRIVER_READY_WAIT;
 
+  // Regular Wifi stuff.
+  var netUtil = WifiNetUtil(controlMessage);
+  var wifiCommand = WifiCommand(controlMessage, manager.ifname);
+
+  // Wifi P2P stuff
+  var p2pManager;
+  if (p2pSupported) {
+    let p2pCommand = WifiCommand(controlMessage, WifiP2pManager.INTERFACE_NAME);
+    p2pManager = WifiP2pManager(p2pCommand, netUtil);
+  }
+
   let wifiService = Cc["@mozilla.org/wifi/service;1"];
   if (wifiService) {
     wifiService = wifiService.getService(Ci.nsIWifiProxyService);
     let interfaces = [manager.ifname];
+    if (p2pSupported) {
+      interfaces.push(WifiP2pManager.INTERFACE_NAME);
+    }
     wifiService.start(wifiListener, interfaces, interfaces.length);
   } else {
     debug("No wifi service component available!");
   }
 
-  var wifiCommand = WifiCommand(controlMessage, manager.ifname);
-  var netUtil = WifiNetUtil(controlMessage);
-
   // Callbacks to invoke when a reply arrives from the wifi service.
   var controlCallbacks = Object.create(null);
   var idgen = 0;
@@ -244,6 +271,7 @@ var WifiManager = (function() {
       wifiCommand.doSetScanMode(true, function(ignore) {
         setBackgroundScan("OFF", function(turned, ignore) {
           reEnableBackgroundScan = turned;
+          manager.handlePreWifiScan();
           wifiCommand.scan(function(ok) {
             wifiCommand.doSetScanMode(false, function(ignore) {
               // The result of scanCommand is the result of the actual SCAN
@@ -255,6 +283,7 @@ var WifiManager = (function() {
       });
       return;
     }
+    manager.handlePreWifiScan();
     wifiCommand.scan(callback);
   }
 
@@ -267,6 +296,7 @@ var WifiManager = (function() {
         if (ok)
           debugEnabled = wanted;
       });
+      p2pManager.setDebug(DEBUG);
     }
   }
 
@@ -755,6 +785,7 @@ var WifiManager = (function() {
         reEnableBackgroundScan = false;
         setBackgroundScan("ON", function() {});
       }
+      manager.handlePostWifiScan();
       notify("scanresultsavailable");
       return true;
     }
@@ -786,6 +817,10 @@ var WifiManager = (function() {
       notify("supplicantconnection");
       callback();
     });
+
+    if (p2pSupported) {
+      manager.enableP2p(function(success) {});
+    }
   }
 
   function prepareForStartup(callback) {
@@ -911,19 +946,27 @@ var WifiManager = (function() {
       // Note these following calls ignore errors. If we fail to kill the
       // supplicant gracefully, then we need to continue telling it to die
       // until it does.
-      manager.state = "DISABLING";
-      wifiCommand.terminateSupplicant(function (ok) {
-        manager.connectionDropped(function () {
-          wifiCommand.stopSupplicant(function (status) {
-            wifiCommand.closeSupplicantConnection(function () {
-              manager.state = "UNINITIALIZED";
-              netUtil.disableInterface(manager.ifname, function (ok) {
-                unloadDriver(WIFI_FIRMWARE_STATION, callback);
+      let doDisableWifi = function() {
+        manager.state = "DISABLING";
+        wifiCommand.terminateSupplicant(function (ok) {
+          manager.connectionDropped(function () {
+            wifiCommand.stopSupplicant(function (status) {
+              wifiCommand.closeSupplicantConnection(function () {
+                manager.state = "UNINITIALIZED";
+                netUtil.disableInterface(manager.ifname, function (ok) {
+                  unloadDriver(WIFI_FIRMWARE_STATION, callback);
+                });
               });
             });
           });
         });
-      });
+      }
+
+      if (p2pSupported) {
+        p2pManager.setEnabled(false, { onDisabled: doDisableWifi });
+      } else {
+        doDisableWifi();
+      }
     }
   }
 
@@ -1135,6 +1178,11 @@ var WifiManager = (function() {
     wifiCommand.saveConfig(callback);
   }
   manager.enableNetwork = function(netId, disableOthers, callback) {
+    if (p2pSupported) {
+      // We have to stop wifi direct scan before associating to an AP.
+      // Otherwise we will get a "REJECT" wpa supplicant event.
+      p2pManager.setScanEnabled(false, function(success) {});
+    }
     wifiCommand.enableNetwork(netId, disableOthers, callback);
   }
   manager.disableNetwork = function(netId, callback) {
@@ -1215,6 +1263,46 @@ var WifiManager = (function() {
     }
   }
 
+  manager.handlePreWifiScan = function() {
+    if (p2pSupported) {
+      // Before doing regular wifi scan, we have to disable wifi direct
+      // scan first. Otherwise we will never get the scan result.
+      p2pManager.blockScan();
+    }
+  };
+
+  manager.handlePostWifiScan = function() {
+    if (p2pSupported) {
+      // After regular wifi scanning, we should restore the restricted
+      // wifi direct scan.
+      p2pManager.unblockScan();
+    }
+  };
+
+  //
+  // Public APIs for P2P.
+  //
+
+  manager.p2pSupported = function() {
+    return p2pSupported;
+  };
+
+  manager.getP2pManager = function() {
+    return p2pManager;
+  };
+
+  manager.enableP2p = function(callback) {
+    p2pManager.setEnabled(true, {
+      onSupplicantConnected: function() {
+        wifiService.waitForEvent(WifiP2pManager.INTERFACE_NAME);
+      },
+
+      onEnabled: function(success) {
+        callback(success);
+      }
+    });
+  };
+
   return manager;
 })();
 
@@ -1497,6 +1585,17 @@ function WifiWorker() {
   this._connectionInfoTimer = null;
   this._reconnectOnDisconnect = false;
 
+  // Create p2pObserver and assign to p2pManager.
+  if (WifiManager.p2pSupported()) {
+    this._p2pObserver = WifiP2pWorkerObserver(WifiManager.getP2pManager());
+    WifiManager.getP2pManager().setObserver(this._p2pObserver);
+
+    // Add DOM message observerd by p2pObserver to the message listener as well.
+    this._p2pObserver.getObservedDOMMessages().forEach((function(msgName) {
+      this._mm.addMessageListener(msgName, this);
+    }).bind(this));
+  }
+
   // Users of instances of nsITimer should keep a reference to the timer until
   // it is no longer needed in order to assure the timer is fired.
   this._callbackTimer = null;
@@ -1529,6 +1628,7 @@ function WifiWorker() {
     // wait for our next command) ensure that background scanning is on and
     // then try again.
     debug("Determined that scanning is stuck, turning on background scanning!");
+    WifiManager.handlePostWifiScan();
     WifiManager.disconnect(function(ok) {});
     self._turnOnBackgroundScan = true;
   }
@@ -2304,6 +2404,15 @@ WifiWorker.prototype = {
     let msg = aMessage.data || {};
     msg.manager = aMessage.target;
 
+    if (WifiManager.p2pSupported()) {
+      // If p2pObserver returns something truthy, return it!
+      // Otherwise, continue to do the rest of tasks.
+      var p2pRet = this._p2pObserver.onDOMMessage(aMessage);
+      if (p2pRet) {
+        return p2pRet;
+      }
+    }
+
     // Note: By the time we receive child-process-shutdown, the child process
     // has already forgotten its permissions so we do this before the
     // permissions check.
@@ -2393,79 +2502,6 @@ WifiWorker.prototype = {
     }).bind(this));
   },
 
-  getWifiScanResults: function(callback) {
-    var count = 0;
-    var timer = null;
-    var self = this;
-
-    self.waitForScan(waitForScanCallback);
-    doScan();
-    function doScan() {
-      WifiManager.scan(true, function (ok) {
-        if (!ok) {
-          if (!timer) {
-            count = 0;
-            timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-          }
-
-          if (count++ >= 3) {
-            timer = null;
-            this.wantScanResults.splice(this.wantScanResults.indexOf(waitForScanCallback), 1);
-            callback.onfailure();
-            return;
-          }
-
-          // Else it's still running, continue waiting.
-          timer.initWithCallback(doScan, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
-          return;
-        }
-      });
-    }
-
-    function waitForScanCallback(networks) {
-      if (networks === null) {
-        callback.onfailure();
-        return;
-      }
-
-      var wifiScanResults = new Array();
-      var net;
-      for (let net in networks) {
-        let value = networks[net];
-        wifiScanResults.push(transformResult(value));
-      }
-      callback.onready(wifiScanResults.length, wifiScanResults);
-    }
-
-    function transformResult(element) {
-      var result = new WifiScanResult();
-      result.connected = false;
-      for (let id in element) {
-        if (id === "__exposedProps__") {
-          continue;
-        }
-        if (id === "security") {
-          result[id] = 0;
-          var security = element[id];
-          for (let j = 0; j < security.length; j++) {
-            if (security[j] === "WPA-PSK") {
-              result[id] |= Ci.nsIWifiScanResult.WPA_PSK;
-            } else if (security[j] === "WPA-EAP") {
-              result[id] |= Ci.nsIWifiScanResult.WPA_EAP;
-            } else if (security[j] === "WEP") {
-              result[id] |= Ci.nsIWifiScanResult.WEP;
-            } else {
-             result[id] = 0;
-            }
-          }
-        } else {
-          result[id] = element[id];
-        }
-      }
-      return result;
-    }
-  },
-
   getKnownNetworks: function(msg) {
     const message = "WifiManager:getKnownNetworks:Return";
     if (!WifiManager.enabled) {
@@ -2722,7 +2758,7 @@ WifiWorker.prototype = {
     let self = this;
     let detail = msg.data;
     if (detail.method === "pbc") {
-      WifiManager.wpsPbc(function(ok) {
+      WifiManager.wpsPbc(WifiManager.ifname, function(ok) {
         if (ok)
           self._sendMessage(message, true, true, msg);
         else
diff --git a/dom/wifi/moz.build b/dom/wifi/moz.build
index c6c8a54e9c74fc9b48ad644729694199b390252d..8f8f3dd116b9356488c3fa95064f62584450471d 100644
--- a/dom/wifi/moz.build
+++ b/dom/wifi/moz.build
@@ -6,6 +6,7 @@
 
 XPIDL_SOURCES += [
     'nsIDOMMozWifiConnectionInfoEvent.idl',
+    'nsIDOMMozWifiP2pStatusChangeEvent.idl',
     'nsIDOMMozWifiStatusChangeEvent.idl',
     'nsIWifi.idl',
     'nsIWifiService.idl',
@@ -16,13 +17,18 @@ XPIDL_MODULE = 'dom_wifi'
 EXTRA_COMPONENTS += [
     'DOMWifiManager.js',
     'DOMWifiManager.manifest',
+    'DOMWifiP2pManager.js',
+    'DOMWifiP2pManager.manifest',
     'WifiWorker.js',
     'WifiWorker.manifest',
 ]
 
 EXTRA_JS_MODULES += [
+    'StateMachine.jsm',
     'WifiCommand.jsm',
     'WifiNetUtil.jsm',
+    'WifiP2pManager.jsm',
+    'WifiP2pWorkerObserver.jsm',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
diff --git a/dom/wifi/nsIDOMMozWifiP2pStatusChangeEvent.idl b/dom/wifi/nsIDOMMozWifiP2pStatusChangeEvent.idl
new file mode 100644
index 0000000000000000000000000000000000000000..12c612578b588b09e73b07dc7342243be3d7f3ea
--- /dev/null
+++ b/dom/wifi/nsIDOMMozWifiP2pStatusChangeEvent.idl
@@ -0,0 +1,24 @@
+/* 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 "nsIDOMEvent.idl"
+
+[scriptable, builtinclass, uuid(82cad910-2019-11e3-8224-0800200c9a66)]
+interface nsIDOMMozWifiP2pStatusChangeEvent : nsIDOMEvent
+{
+  /**
+   * The mac address of the peer whose status has just changed.
+   */
+  readonly attribute DOMString peerAddress;
+
+  [noscript] void initMozWifiP2pStatusChangeEvent(in DOMString aType,
+                                                  in boolean aCanBubble,
+                                                  in boolean aCancelable,
+                                                  in DOMString aPeerAddress);
+};
+
+dictionary MozWifiP2pStatusChangeEventInit : EventInit
+{
+  DOMString peerAddress;
+};
diff --git a/js/xpconnect/src/event_impl_gen.conf.in b/js/xpconnect/src/event_impl_gen.conf.in
index cafb46e14458e6180f271f31d5adeb75e734c74a..ab71feb0a3f0201ae410da4c24a58f9545eaaff6 100644
--- a/js/xpconnect/src/event_impl_gen.conf.in
+++ b/js/xpconnect/src/event_impl_gen.conf.in
@@ -24,6 +24,7 @@ simple_events = [
     'StyleSheetChangeEvent',
     'StyleSheetApplicableStateChangeEvent',
 #ifdef MOZ_WIDGET_GONK
+    'MozWifiP2pStatusChangeEvent',
     'MozWifiStatusChangeEvent',
     'MozWifiConnectionInfoEvent',
 #endif
diff --git a/modules/libpref/src/init/all.js b/modules/libpref/src/init/all.js
index b073d844ec2c5c6ed857172571ee48a06f4f8cb1..5075056d729539356209cfdbec322062abc6eeb4 100644
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -272,6 +272,12 @@ pref("media.mediasource.enabled", false);
 #ifdef MOZ_WEBSPEECH
 pref("media.webspeech.recognition.enable", false);
 #endif
+#ifdef MOZ_WEBM_ENCODER
+pref("media.encoder.webm.enabled", true);
+#endif
+#ifdef MOZ_OMX_ENCODER
+pref("media.encoder.omx.enabled", true);
+#endif
 
 // Whether to enable Web Audio support
 pref("media.webaudio.enabled", true);
diff --git a/services/fxaccounts/FxAccounts.jsm b/services/fxaccounts/FxAccounts.jsm
index 68d2a61543868b70cb5396581c33844606ef4938..c1af92496a51df5c5fa90e418dab33a360b6b735 100644
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -111,7 +111,7 @@ InternalMethods.prototype = {
     this.abortExistingFlow();
     this.signedInUser = null; // clear in-memory cache
     return this.signedInUserStorage.set(null).then(() => {
-      this.notifyObservers("fxaccounts:onlogout");
+      this.notifyObservers(ONLOGOUT_NOTIFICATION);
     });
   },
 
@@ -198,7 +198,7 @@ InternalMethods.prototype = {
       // We are now ready for business. This should only be invoked once
       // per setSignedInUser(), regardless of whether we've rebooted since
       // setSignedInUser() was called.
-      internal.notifyObservers("fxaccounts:onlogin");
+      internal.notifyObservers(ONLOGIN_NOTIFICATION);
       return data;
     }.bind(this));
   },
@@ -347,7 +347,7 @@ InternalMethods.prototype = {
   },
 
   notifyObservers: function(topic) {
-    log.debug("Notifying observers of user login");
+    log.debug("Notifying observers of " + topic);
     Services.obs.notifyObservers(null, topic, null);
   },
 
diff --git a/services/fxaccounts/FxAccountsCommon.js b/services/fxaccounts/FxAccountsCommon.js
index 1c65449bcaee1b99b38c48f9765ca9c10754b234..474418b097e256a555bc9ed5993b5e53f4f2884d 100644
--- a/services/fxaccounts/FxAccountsCommon.js
+++ b/services/fxaccounts/FxAccountsCommon.js
@@ -41,6 +41,10 @@ this.KEY_LIFETIME       = 1000 * 3600 * 12; // 12 hours
 this.POLL_SESSION       = 1000 * 60 * 5;    // 5 minutes
 this.POLL_STEP          = 1000 * 3;         // 3 seconds
 
+// Observer notifications.
+this.ONLOGIN_NOTIFICATION = "fxaccounts:onlogin";
+this.ONLOGOUT_NOTIFICATION = "fxaccounts:onlogout";
+
 // Server errno.
 // From https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#response-format
 this.ERRNO_ACCOUNT_ALREADY_EXISTS     = 101;
diff --git a/services/fxaccounts/FxAccountsManager.jsm b/services/fxaccounts/FxAccountsManager.jsm
index cbae6cd0f959590fb6cc671ebc9bebda1e346b1b..9d664e9fb5c5e4e4713cf2323bdd92f2053969bf 100644
--- a/services/fxaccounts/FxAccountsManager.jsm
+++ b/services/fxaccounts/FxAccountsManager.jsm
@@ -26,6 +26,19 @@ XPCOMUtils.defineLazyModuleGetter(this, "FxAccountsClient",
 
 this.FxAccountsManager = {
 
+  init: function() {
+    Services.obs.addObserver(this, ONLOGOUT_NOTIFICATION, false);
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic !== ONLOGOUT_NOTIFICATION) {
+      return;
+    }
+
+    // Remove the cached session if we get a logout notification.
+    this._activeSession = null;
+  },
+
   // We don't really need to save fxAccounts instance but this way we allow
   // to mock FxAccounts from tests.
   _fxAccounts: fxAccounts,
@@ -156,22 +169,25 @@ this.FxAccountsManager = {
       return Promise.resolve();
     }
 
-    return this._fxAccounts.signOut(this._activeSession.sessionToken).then(
+    // We clear the local session cache as soon as we get the onlogout
+    // notification triggered within FxAccounts.signOut, so we save the
+    // session token value to be able to remove the remote server session
+    // in case that we have network connection.
+    let sessionToken = this._activeSession.sessionToken;
+
+    return this._fxAccounts.signOut(sessionToken).then(
       () => {
-        // If there is no connection, removing the local session should be
-        // enough. The client can create new sessions up to the limit (100?).
+        // At this point the local session should already be removed.
+
+        // The client can create new sessions up to the limit (100?).
         // Orphaned tokens on the server will eventually be garbage collected.
         if (Services.io.offline) {
-          this._activeSession = null;
           return Promise.resolve();
         }
         // Otherwise, we try to remove the remote session.
         let client = this._createFxAccountsClient();
-        return client.signOut(this._activeSession.sessionToken).then(
+        return client.signOut(sessionToken).then(
           result => {
-            // Even if there is a remote server error, we remove the local
-            // session.
-            this._activeSession = null;
             let error = this._getError(result);
             if (error) {
               return Promise.reject({
@@ -183,9 +199,6 @@ this.FxAccountsManager = {
             return Promise.resolve();
           },
           reason => {
-            // Even if there is a remote server error, we remove the local
-            // session.
-            this._activeSession = null;
             return this._serverError(reason);
           }
         );
@@ -413,3 +426,5 @@ this.FxAccountsManager = {
     );
   }
 };
+
+FxAccountsManager.init();
diff --git a/services/fxaccounts/tests/xpcshell/test_manager.js b/services/fxaccounts/tests/xpcshell/test_manager.js
index 0261f6826e998e3412297287bf96f1cb0bdff86a..cac8fcdda43886784cb4be66211954ec1d7151e4 100644
--- a/services/fxaccounts/tests/xpcshell/test_manager.js
+++ b/services/fxaccounts/tests/xpcshell/test_manager.js
@@ -121,6 +121,7 @@ FxAccountsManager._fxAccounts = {
   signOut: function() {
     let deferred = Promise.defer();
     this._signedInUser = null;
+    Services.obs.notifyObservers(null, ONLOGOUT_NOTIFICATION, null);
     deferred.resolve();
     return deferred.promise;
   }
@@ -581,3 +582,13 @@ add_test(function(test_queryAccount_no_accountId) {
     }
   );
 });
+
+add_test(function() {
+  do_print("= Test 23 | fxaccounts:onlogout notification =");
+  do_check_true(FxAccountsManager._activeSession != null);
+  Services.obs.notifyObservers(null, ONLOGOUT_NOTIFICATION, null);
+  do_execute_soon(function() {
+    do_check_null(FxAccountsManager._activeSession);
+    run_next_test();
+  });
+});
diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js
index 2ee073b5ced33d13289ea6deac4e0204747a1dc2..9794e095ab8ca8973ca22e2c90788a2b228187a2 100644
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -40,6 +40,7 @@ let curFrame = content;
 let previousFrame = null;
 let elementManager = new ElementManager([]);
 let importedScripts = null;
+let inputSource = null;
 
 // The sandbox we execute test scripts in. Gets lazily created in
 // createExecuteContentSandbox().
@@ -189,6 +190,12 @@ function newSession(msg) {
   resetValues();
   if (isB2G) {
     readyStateTimer.initWithCallback(waitForReady, 100, Ci.nsITimer.TYPE_ONE_SHOT);
+    // We have to set correct mouse event source to MOZ_SOURCE_TOUCH
+    // to offer a way for event listeners to differentiate
+    // events being the result of a physical mouse action.
+    // This is especially important for the touch event shim,
+    // in order to prevent creating touch event for these fake mouse events.
+    inputSource = Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH;
   }
 }
  
@@ -664,7 +671,7 @@ function emitTouchEvent(type, touch) {
     marionetteLogObj.clearLogs();
     */
     let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
-    domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.screenX], [touch.screenY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
+    domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
   }
 }
 
@@ -672,10 +679,10 @@ function emitTouchEvent(type, touch) {
  * This function emit mouse event
  *   @param: doc is the current document
  *           type is the type of event to dispatch
- *           detail is the number of clicks, button notes the mouse button
+ *           clickCount is the number of clicks, button notes the mouse button
  *           elClientX and elClientY are the coordinates of the mouse relative to the viewport
  */
-function emitMouseEvent(doc, type, elClientX, elClientY, detail, button) {
+function emitMouseEvent(doc, type, elClientX, elClientY, clickCount, button) {
   if (!wasInterrupted()) {
     let loggingInfo = "emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
     dumpLog(loggingInfo);
@@ -686,11 +693,9 @@ function emitMouseEvent(doc, type, elClientX, elClientY, detail, button) {
                     {log: elementManager.wrapValue(marionetteLogObj.getLogs())});
     marionetteLogObj.clearLogs();
     */
-    detail = detail || 1;
-    button = button || 0;
     let win = doc.defaultView;
-    // Figure out the element the mouse would be over at (x, y)
-    utils.synthesizeMouseAtPoint(elClientX, elClientY, {type: type, button: button, clickCount: detail}, win);
+    let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
+    domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1, 0, false, 0, inputSource);
   }
 }
 
@@ -1029,7 +1034,7 @@ function emitMultiEvents(type, touch, touches) {
   // Create changed touches
   let changedTouches = doc.createTouchList(touch);
   // Create the event object
-  let event = curFrame.document.createEvent('TouchEvent');
+  let event = doc.createEvent('TouchEvent');
   event.initTouchEvent(type,
                        true,
                        true,
diff --git a/testing/mochitest/b2g.json b/testing/mochitest/b2g.json
index 236646fafbe98fe3ff44e831ef88943b77c40a56..0549e5c634819fc13105089b73d09c914e10b493 100644
--- a/testing/mochitest/b2g.json
+++ b/testing/mochitest/b2g.json
@@ -97,7 +97,6 @@
 
     "content/base/test/test_XHRSendData.html":"seems to stall",
     "content/base/test/test_XHR_parameters.html":"86 total, 4 failing - testing mozAnon - got false, expected true",
-    "content/base/test/test_XHR_system.html":"12 total, 2 failing - .mozSystem == true - got false, expected true + ",
 
     "content/base/test/test_bug431701.html":"xmlhttprequest causes crash, bug 902271",
     "content/base/test/test_bug422537.html":"xmlhttprequest causes crash, bug 902271",
@@ -238,18 +237,13 @@
     "content/base/test/test_bug424359-2.html":"",
     "content/base/test/test_mixed_content_blocker_bug803225.html":"",
     "content/html/document/test/test_non-ascii-cookie.html":"",
-    "content/html/document/test/test_document.watch.html":"expects document.cookie setting to work",
 
     "docshell/test/navigation/test_bug13871.html":"",
     "docshell/test/navigation/test_bug270414.html":"",
-    "docshell/test/navigation/test_bug344861.html":"",
-    "docshell/test/navigation/test_bug386782.html":"",
     "docshell/test/navigation/test_not-opener.html":"",
-    "docshell/test/navigation/test_reserved.html":"",
     "docshell/test/test_bug413310.html":"",
 
     "dom/imptests/html/webgl":"",
-    "dom/battery/test/test_battery_basics.html":"",
 
     "dom/browser-element/mochitest/test_browserElement_inproc_Alert.html":"",
     "dom/browser-element/mochitest/test_browserElement_inproc_AppFramePermission.html":"",
@@ -341,16 +335,10 @@
     "dom/media/tests/mochitest/test_peerConnection_throwInCallbacks.html":"",
     "dom/media/tests/ipc/test_ipc.html":"nested ipc not working",
 
-    "dom/network/tests/test_networkstats_basics.html":"Will be fixed in bug 858005",
     "dom/permission/tests/test_permission_basics.html":"https not working, bug 907770",
 
-    "dom/tests/mochitest/bugs/test_bug335976.xhtml":"",
-    "dom/tests/mochitest/bugs/test_bug369306.html":"test timed out, can't focus back from popup window to opener?",
-    "dom/tests/mochitest/bugs/test_bug396843.html":"",
     "dom/tests/mochitest/bugs/test_bug406375.html":"",
-
     "dom/tests/mochitest/bugs/test_bug427744.html":"",
-    "dom/tests/mochitest/bugs/test_bug641552.html":"",
     "dom/tests/mochitest/bugs/test_sizetocontent_clamp.html": "Windows can't change size on B2G",
     "dom/tests/mochitest/bugs/test_resize_move_windows.html": "Windows can't change size and position on B2G",
     "dom/tests/mochitest/bugs/test_window_bar.html":"",
@@ -398,12 +386,10 @@
 
     "dom/tests/mochitest/pointerlock/test_pointerlock-api.html":"window.open focus issues (using fullscreen)",
     "dom/tests/mochitest/webapps/test_bug_779982.html":"",
-    "dom/tests/mochitest/whatwg/test_postMessage_closed.html":"bug 894914 - wrong data - got FAIL, expected message",
 
     "dom/workers/test/test_suspend.html":"test timed out, might need more time",
     "dom/workers/test/test_xhr_parameters.html":"",
     "dom/workers/test/test_xhr_system.html":"",
-    "dom/workers/test/test_closeOnGC.html": "times out",
     "dom/workers/test/test_errorPropagation.html":"times out",
     "dom/workers/test/test_errorwarning.html":"Failed to load script: errorwarning_worker.js",
     "dom/workers/test/test_fibonacci.html":"Failed to load script: fibonacci_worker.js",
@@ -415,22 +401,16 @@
     "layout/base/tests/test_mozPaintCount.html":"depends on plugins support",
     "layout/forms/test/test_bug348236.html":"select form control popup",
     "layout/forms/test/test_bug446663.html":"needs copy support",
-    "layout/forms/test/test_bug564115.html":"times out on window.open and focus event",
     "layout/forms/test/test_bug571352.html":"shift-click multi-select not working?",
     "layout/forms/test/test_textarea_resize.html":"resizing textarea not available in b2g",
     "layout/forms/test/test_bug903715.html":"select elements don't use an in-page popup in B2G",
-    "layout/forms/test/test_listcontrol_search.html" : "select elements don't use an in-page popup in B2G",
     "layout/generic/test/test_bug392746.html":"ctrl mouse select not working in b2g",
-    "layout/generic/test/test_bug470212.html":"shift mouse select not working in b2g",
-    "layout/generic/test/test_bug514732.html":"times out, also on Android",
     "layout/generic/test/test_bug791616.html":"Target should not have scrolled - got 114.10000610351562, expected 115.39999389648438",
     "layout/generic/test/test_invalidate_during_plugin_paint.html":"plugins not supported",
-    "layout/generic/test/test_page_scroll_with_fixed_pos.html":"opened window too small?",
     "layout/generic/test/test_plugin_focus.html":"plugins not supported",
     "layout/generic/test/test_plugin_mouse_coords.html":"plugins not supported",
     "layout/generic/test/test_selection_expanding.html":"mouse selection not working",
 
-    "layout/style/test/test_rule_insertion.html":"monospace and serif text have sufficiently different widths",
     "layout/style/test/test_transitions_per_property.html":"times out, needs more time + various failures",
 
     "content/html/content/test/test_bug209275.xhtml":"timed out, 47 tests, bug 870262, :visited support",
diff --git a/testing/mochitest/tests/SimpleTest/EventUtils.js b/testing/mochitest/tests/SimpleTest/EventUtils.js
index bc1564a04f156b2988c6e287b2d0cb32333766d2..4fb33590af159aa23628122fe0691865046ba452 100644
--- a/testing/mochitest/tests/SimpleTest/EventUtils.js
+++ b/testing/mochitest/tests/SimpleTest/EventUtils.js
@@ -249,9 +249,13 @@ function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
     var modifiers = _parseModifiers(aEvent);
     var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
     var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
+    var synthesized = ("isSynthesized" in aEvent) ? aEvent.isSynthesized : true;
 
     if (("type" in aEvent) && aEvent.type) {
-      defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
+      defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button,
+                                              clickCount, modifiers, false,
+                                              pressure, inputSource,
+                                              synthesized);
     }
     else {
       utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
diff --git a/toolkit/devtools/touch-events.js b/toolkit/devtools/touch-events.js
index 72283d78c5ff8dac7b0bb41cb61ead5a6b8b0cbd..192cea32a55d5e4ccdeb0fcf383e02b58e8b36dd 100644
--- a/toolkit/devtools/touch-events.js
+++ b/toolkit/devtools/touch-events.js
@@ -11,14 +11,19 @@ let handlerCount = 0;
 
 let orig_w3c_touch_events = Services.prefs.getIntPref('dom.w3c_touch_events.enabled');
 
+let trackedWindows = new WeakMap();
+
 // =================== Touch ====================
 // Simulate touch events on desktop
 function TouchEventHandler (window) {
+  // Returns an already instanciated handler for this window
+  let cached = trackedWindows.get(window);
+  if (cached) {
+    return cached;
+  }
+
   let contextMenuTimeout = 0;
 
-  // This guard is used to not re-enter the events processing loop for
-  // self dispatched events
-  let ignoreEvents = false;
 
   let threshold = 25;
   try {
@@ -32,30 +37,35 @@ function TouchEventHandler (window) {
 
   let TouchEventHandler = {
     enabled: false,
-    events: ['mousedown', 'mousemove', 'mouseup', 'click'],
+    events: ['mousedown', 'mousemove', 'mouseup'],
     start: function teh_start() {
+      if (this.enabled)
+        return false;
+      this.enabled = true;
       let isReloadNeeded = Services.prefs.getIntPref('dom.w3c_touch_events.enabled') != 1;
-      handlerCount++;
       Services.prefs.setIntPref('dom.w3c_touch_events.enabled', 1);
-      this.enabled = true;
       this.events.forEach((function(evt) {
-        window.addEventListener(evt, this, true);
+        // Only listen trusted events to prevent messing with
+        // event dispatched manually within content documents
+        window.addEventListener(evt, this, true, false);
       }).bind(this));
       return isReloadNeeded;
     },
     stop: function teh_stop() {
-      handlerCount--;
-      if (handlerCount == 0)
-        Services.prefs.setIntPref('dom.w3c_touch_events.enabled', orig_w3c_touch_events);
+      if (!this.enabled)
+        return;
       this.enabled = false;
+      Services.prefs.setIntPref('dom.w3c_touch_events.enabled', orig_w3c_touch_events);
       this.events.forEach((function(evt) {
         window.removeEventListener(evt, this, true);
       }).bind(this));
     },
     handleEvent: function teh_handleEvent(evt) {
-      if (evt.button || ignoreEvents ||
-          evt.mozInputSource == Ci.nsIDOMMouseEvent.MOZ_SOURCE_UNKNOWN)
+      // Ignore all but real mouse event coming from physical mouse
+      // (especially ignore mouse event being dispatched from a touch event)
+      if (evt.button || evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE || evt.isSynthesized) {
         return;
+      }
 
       // The gaia system window use an hybrid system even on the device which is
       // a mix of mouse/touch events. So let's not cancel *all* mouse events
@@ -105,6 +115,13 @@ function TouchEventHandler (window) {
 
           content.clearTimeout(contextMenuTimeout);
           type = 'touchend';
+
+          // Only register click listener after mouseup to ensure
+          // catching only real user click. (Especially ignore click
+          // being dispatched on form submit)
+          if (evt.detail == 1) {
+            window.addEventListener('click', this, true, false);
+          }
           break;
 
         case 'click':
@@ -113,6 +130,8 @@ function TouchEventHandler (window) {
           evt.preventDefault();
           evt.stopImmediatePropagation();
 
+          window.removeEventListener('click', this, true, false);
+
           if (this.cancelClick)
             return;
 
@@ -141,7 +160,7 @@ function TouchEventHandler (window) {
       let content = this.getContent(evt.target);
       var utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
                          .getInterface(Ci.nsIDOMWindowUtils);
-      utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true);
+      utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0, Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
     },
     sendContextMenu: function teh_sendContextMenu(target, x, y, delay) {
       let doc = target.ownerDocument;
@@ -159,6 +178,26 @@ function TouchEventHandler (window) {
       return timeout;
     },
     sendTouchEvent: function teh_sendTouchEvent(evt, target, name) {
+      // When running OOP b2g desktop, we need to send the touch events
+      // using the mozbrowser api on the unwrapped frame.
+      if (target.localName == "iframe" && target.mozbrowser === true) {
+        if (name == "touchstart") {
+          this.touchstartTime = Date.now();
+        } else if (name == "touchend") {
+          // If we have a 'fast' tap, don't send a click as both will be turned
+          // into a click and that breaks eg. checkboxes.
+          if (Date.now() - this.touchstartTime < delay) {
+            this.cancelClick = true;
+          }
+        }
+        let unwraped = XPCNativeWrapper.unwrap(target);
+        unwraped.sendTouchEvent(name, [0],                    // event type, id
+                                [evt.clientX], [evt.clientY], // x, y
+                                [1], [1],                     // rx, ry
+                                [0], [0],                     // rotation, force
+                                1);                           // count
+        return;
+      }
       let document = target.ownerDocument;
       let content = this.getContent(target);
 
@@ -179,15 +218,10 @@ function TouchEventHandler (window) {
     },
     getContent: function teh_getContent(target) {
       let win = target.ownerDocument.defaultView;
-      let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIWebNavigation)
-                    .QueryInterface(Ci.nsIDocShellTreeItem);
-      let topDocShell = docShell.sameTypeRootTreeItem;
-      topDocShell.QueryInterface(Ci.nsIDocShell);
-      let top = topDocShell.contentViewer.DOMDocument.defaultView;
-      return top;
+      return win;
     }
   };
+  trackedWindows.set(window, TouchEventHandler);
 
   return TouchEventHandler;
 }
diff --git a/widget/TextEvents.h b/widget/TextEvents.h
index 7fd7d55e2193c87d7cdcb4472af0dab6a24eee74..92138f713839b27e703578bb5269b94d60759be9 100644
--- a/widget/TextEvents.h
+++ b/widget/TextEvents.h
@@ -32,6 +32,8 @@ enum
 
 #undef NS_DEFINE_VK
 
+#define kLatestSeqno UINT32_MAX
+
 namespace mozilla {
 
 namespace dom {
@@ -166,18 +168,25 @@ private:
   friend class plugins::PPluginInstanceChild;
 
   WidgetTextEvent()
+    : mSeqno(kLatestSeqno)
+    , rangeCount(0)
+    , rangeArray(nullptr)
+    , isChar(false)
   {
   }
 
 public:
-  uint32_t seqno;
+  uint32_t mSeqno;
 
 public:
   virtual WidgetTextEvent* AsTextEvent() MOZ_OVERRIDE { return this; }
 
-  WidgetTextEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget) :
-    WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_TEXT_EVENT),
-    rangeCount(0), rangeArray(nullptr), isChar(false)
+  WidgetTextEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget)
+    : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_TEXT_EVENT)
+    , mSeqno(kLatestSeqno)
+    , rangeCount(0)
+    , rangeArray(nullptr)
+    , isChar(false)
   {
   }
 
@@ -217,11 +226,12 @@ private:
   friend class mozilla::dom::PBrowserChild;
 
   WidgetCompositionEvent()
+    : mSeqno(kLatestSeqno)
   {
   }
 
 public:
-  uint32_t seqno;
+  uint32_t mSeqno;
 
 public:
   virtual WidgetCompositionEvent* AsCompositionEvent() MOZ_OVERRIDE
@@ -230,8 +240,9 @@ public:
   }
 
   WidgetCompositionEvent(bool aIsTrusted, uint32_t aMessage,
-                         nsIWidget* aWidget) :
-    WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_COMPOSITION_EVENT)
+                         nsIWidget* aWidget)
+    : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_COMPOSITION_EVENT)
+    , mSeqno(kLatestSeqno)
   {
     // XXX compositionstart is cancelable in draft of DOM3 Events.
     //     However, it doesn't make sense for us, we cannot cancel composition
@@ -376,12 +387,18 @@ private:
   friend class mozilla::dom::PBrowserChild;
 
   WidgetSelectionEvent()
+    : mSeqno(kLatestSeqno)
+    , mOffset(0)
+    , mLength(0)
+    , mReversed(false)
+    , mExpandToClusterBoundary(true)
+    , mSucceeded(false)
   {
     MOZ_CRASH("WidgetSelectionEvent is created without proper arguments");
   }
 
 public:
-  uint32_t seqno;
+  uint32_t mSeqno;
 
 public:
   virtual WidgetSelectionEvent* AsSelectionEvent() MOZ_OVERRIDE
@@ -389,9 +406,14 @@ public:
     return this;
   }
 
-  WidgetSelectionEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget) :
-    WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_SELECTION_EVENT),
-    mExpandToClusterBoundary(true), mSucceeded(false)
+  WidgetSelectionEvent(bool aIsTrusted, uint32_t aMessage, nsIWidget* aWidget)
+    : WidgetGUIEvent(aIsTrusted, aMessage, aWidget, NS_SELECTION_EVENT)
+    , mSeqno(kLatestSeqno)
+    , mOffset(0)
+    , mLength(0)
+    , mReversed(false)
+    , mExpandToClusterBoundary(true)
+    , mSucceeded(false)
   {
   }
 
diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h
index 55980a74e0463fcd81d43e17f0f4b3710907b9a4..22d326110454f94882e7ed128e8dae6ae6403e95 100644
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -386,7 +386,7 @@ struct ParamTraits<mozilla::WidgetTextEvent>
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
-    WriteParam(aMsg, aParam.seqno);
+    WriteParam(aMsg, aParam.mSeqno);
     WriteParam(aMsg, aParam.theText);
     WriteParam(aMsg, aParam.isChar);
     WriteParam(aMsg, aParam.rangeCount);
@@ -398,7 +398,7 @@ struct ParamTraits<mozilla::WidgetTextEvent>
   {
     if (!ReadParam(aMsg, aIter,
                    static_cast<mozilla::WidgetGUIEvent*>(aResult)) ||
-        !ReadParam(aMsg, aIter, &aResult->seqno) ||
+        !ReadParam(aMsg, aIter, &aResult->mSeqno) ||
         !ReadParam(aMsg, aIter, &aResult->theText) ||
         !ReadParam(aMsg, aIter, &aResult->isChar) ||
         !ReadParam(aMsg, aIter, &aResult->rangeCount))
@@ -436,7 +436,7 @@ struct ParamTraits<mozilla::WidgetCompositionEvent>
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
-    WriteParam(aMsg, aParam.seqno);
+    WriteParam(aMsg, aParam.mSeqno);
     WriteParam(aMsg, aParam.data);
   }
 
@@ -444,7 +444,7 @@ struct ParamTraits<mozilla::WidgetCompositionEvent>
   {
     return ReadParam(aMsg, aIter,
                      static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
-           ReadParam(aMsg, aIter, &aResult->seqno) &&
+           ReadParam(aMsg, aIter, &aResult->mSeqno) &&
            ReadParam(aMsg, aIter, &aResult->data);
   }
 };
@@ -493,7 +493,7 @@ struct ParamTraits<mozilla::WidgetSelectionEvent>
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, static_cast<mozilla::WidgetGUIEvent>(aParam));
-    WriteParam(aMsg, aParam.seqno);
+    WriteParam(aMsg, aParam.mSeqno);
     WriteParam(aMsg, aParam.mOffset);
     WriteParam(aMsg, aParam.mLength);
     WriteParam(aMsg, aParam.mReversed);
@@ -505,7 +505,7 @@ struct ParamTraits<mozilla::WidgetSelectionEvent>
   {
     return ReadParam(aMsg, aIter,
                      static_cast<mozilla::WidgetGUIEvent*>(aResult)) &&
-           ReadParam(aMsg, aIter, &aResult->seqno) &&
+           ReadParam(aMsg, aIter, &aResult->mSeqno) &&
            ReadParam(aMsg, aIter, &aResult->mOffset) &&
            ReadParam(aMsg, aIter, &aResult->mLength) &&
            ReadParam(aMsg, aIter, &aResult->mReversed) &&
diff --git a/widget/xpwidgets/PuppetWidget.cpp b/widget/xpwidgets/PuppetWidget.cpp
index 211bd3b384e05148ec09852e865a554a5492fba6..326bebeca301f041547bef3e1144279e811d6f88 100644
--- a/widget/xpwidgets/PuppetWidget.cpp
+++ b/widget/xpwidgets/PuppetWidget.cpp
@@ -273,25 +273,26 @@ PuppetWidget::DispatchEvent(WidgetGUIEvent* event, nsEventStatus& aStatus)
   if (event->message == NS_COMPOSITION_START) {
     mIMEComposing = true;
   }
+  uint32_t seqno = kLatestSeqno;
   switch (event->eventStructType) {
   case NS_COMPOSITION_EVENT:
-    mIMELastReceivedSeqno = event->AsCompositionEvent()->seqno;
-    if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
-      return NS_OK;
+    seqno = event->AsCompositionEvent()->mSeqno;
     break;
   case NS_TEXT_EVENT:
-    mIMELastReceivedSeqno = event->AsTextEvent()->seqno;
-    if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
-      return NS_OK;
+    seqno = event->AsTextEvent()->mSeqno;
     break;
   case NS_SELECTION_EVENT:
-    mIMELastReceivedSeqno = event->AsSelectionEvent()->seqno;
-    if (mIMELastReceivedSeqno < mIMELastBlurSeqno)
-      return NS_OK;
+    seqno = event->AsSelectionEvent()->mSeqno;
     break;
   default:
     break;
   }
+  if (seqno != kLatestSeqno) {
+    mIMELastReceivedSeqno = seqno;
+    if (mIMELastReceivedSeqno < mIMELastBlurSeqno) {
+      return NS_OK;
+    }
+  }
 
   if (mAttachedWidgetListener) {
     aStatus = mAttachedWidgetListener->HandleEvent(event, mUseAttachedEvents);
@@ -352,7 +353,7 @@ PuppetWidget::IMEEndComposition(bool aCancel)
   nsEventStatus status;
   WidgetTextEvent textEvent(true, NS_TEXT_TEXT, this);
   InitEvent(textEvent, nullptr);
-  textEvent.seqno = mIMELastReceivedSeqno;
+  textEvent.mSeqno = mIMELastReceivedSeqno;
   // SendEndIMEComposition is always called since ResetInputState
   // should always be called even if we aren't composing something.
   if (!mTabChild ||
@@ -367,7 +368,7 @@ PuppetWidget::IMEEndComposition(bool aCancel)
 
   WidgetCompositionEvent compEvent(true, NS_COMPOSITION_END, this);
   InitEvent(compEvent, nullptr);
-  compEvent.seqno = mIMELastReceivedSeqno;
+  compEvent.mSeqno = mIMELastReceivedSeqno;
   DispatchEvent(&compEvent, status);
   return NS_OK;
 }