diff --git a/b2g/app/b2g.js b/b2g/app/b2g.js
index faa86541ceb0..5e9d3924c1e7 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 000000000000..311d2e886c8b
--- /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 8bab69a3bd7b..0cdae8285d9f 100644
--- a/b2g/chrome/content/shell.html
+++ b/b2g/chrome/content/shell.html
@@ -19,7 +19,9 @@
src="chrome://browser/content/shell.js">
#ifndef MOZ_WIDGET_GONK
-
+
+
diff --git a/b2g/chrome/jar.mn b/b2g/chrome/jar.mn
index 632668d3f085..4da1121275e3 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 73b4dca10f21..bd36f1f9db7e 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 23243a990137..f0ea2d6473d3 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 9c461d1aabb8..4e990d048e37 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 4bdc4fe82a63..31fef6d64b22 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);
}
diff --git a/content/media/encoder/MediaEncoder.cpp b/content/media/encoder/MediaEncoder.cpp
index a04c992cfde6..ea50fc20413c 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 933363b643eb..f4e1d3087905 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 ce97ad23ae6a..e62a5d05eff4 100644
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1778,6 +1778,26 @@ Navigator::HasIccManagerSupport(JSContext* /* unused */,
nsCOMPtr 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 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 534d3c615be1..aa9327145167 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 25c8b0db5d3a..a8c095ec9413 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 512b0837ef02..21ddf7ee3008 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 b8b227fe8e75..2bbe15678868 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 000000000000..818144ebc32f
--- /dev/null
+++ b/dom/base/test/test_domwindowutils.html
@@ -0,0 +1,84 @@
+
+
+
+
+ Test for DOMWindowUtils
+
+
+
+
+
+
+
+
+
+
diff --git a/dom/events/nsDOMEvent.h b/dom/events/nsDOMEvent.h
index fbcd9341d498..5f4993b21cbc 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 8dd3091a76fa..75cbdbbebdf0 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 e6b8180114f6..22c1452ddfcd 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 b08d0a5814cc..40af3079f3d5 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 2ba6c16109ac..16dcea38413b 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 e4de5ab0ecf3..74e550e17ae1 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 362010e387aa..4bd47353c5b8 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 77c8e4ae902f..acd9c7eadf62 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 0de1b3eb6518..cbde08f20aff 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 callToNotify = mCalls[0];
+ MOZ_ASSERT(callToNotify && callToNotify->ServiceId() == aServiceId);
- nsRefPtr 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 3603136ccfb8..e0bdda4a5883 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 164415896d13..bc5f1f0e5fb6 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 000000000000..400fa1fd80c1
--- /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 000000000000..24fbc4b37d29
--- /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 5571d05faba0..e7b8d814b819 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 000000000000..a485035a0a58
--- /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 000000000000..bc80efa489b9
--- /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 000000000000..ab743a30348e
--- /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 b7a550566e6a..6456628e1394 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 c672183e4ce3..d79542ceda6f 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..dns1");
return;
}
setProperty("net." + name + ".dns2", ipToString(data.dns2),
function(ok) {
if (!ok) {
+ debug("Unable to set net..dns2");
return;
}
setProperty("net." + name + ".gw", ipToString(data.gateway),
function(ok) {
if (!ok) {
+ debug("Unable to set net..gw");
return;
}
callback({ info: data });
diff --git a/dom/wifi/WifiP2pManager.jsm b/dom/wifi/WifiP2pManager.jsm
new file mode 100644
index 000000000000..93499cfacb12
--- /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 000000000000..69d0edead4f9
--- /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 a92ba15a2e66..9bd354156f6d 100644
--- a/dom/wifi/WifiUtils.cpp
+++ b/dom/wifi/WifiUtils.cpp
@@ -5,6 +5,7 @@
#include "WifiUtils.h"
#include
#include
+#include
#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 57e58a20584e..824e18ddf039 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 c6c8a54e9c74..8f8f3dd116b9 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 000000000000..12c612578b58
--- /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 cafb46e14458..ab71feb0a3f0 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 b073d844ec2c..5075056d7295 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 68d2a6154386..c1af92496a51 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 1c65449bcaee..474418b097e2 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 cbae6cd0f959..9d664e9fb5c5 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 0261f6826e99..cac8fcdda438 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 2ee073b5ced3..9794e095ab8c 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 236646fafbe9..0549e5c63481 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 bc1564a04f15..4fb33590af15 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 72283d78c5ff..192cea32a55d 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() {
- let isReloadNeeded = Services.prefs.getIntPref('dom.w3c_touch_events.enabled') != 1;
- handlerCount++;
- Services.prefs.setIntPref('dom.w3c_touch_events.enabled', 1);
+ if (this.enabled)
+ return false;
this.enabled = true;
+ let isReloadNeeded = Services.prefs.getIntPref('dom.w3c_touch_events.enabled') != 1;
+ Services.prefs.setIntPref('dom.w3c_touch_events.enabled', 1);
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 7fd7d55e2193..92138f713839 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 55980a74e046..22d326110454 100644
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -386,7 +386,7 @@ struct ParamTraits
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast(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
{
if (!ReadParam(aMsg, aIter,
static_cast(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
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast(aParam));
- WriteParam(aMsg, aParam.seqno);
+ WriteParam(aMsg, aParam.mSeqno);
WriteParam(aMsg, aParam.data);
}
@@ -444,7 +444,7 @@ struct ParamTraits
{
return ReadParam(aMsg, aIter,
static_cast(aResult)) &&
- ReadParam(aMsg, aIter, &aResult->seqno) &&
+ ReadParam(aMsg, aIter, &aResult->mSeqno) &&
ReadParam(aMsg, aIter, &aResult->data);
}
};
@@ -493,7 +493,7 @@ struct ParamTraits
static void Write(Message* aMsg, const paramType& aParam)
{
WriteParam(aMsg, static_cast(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
{
return ReadParam(aMsg, aIter,
static_cast(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 211bd3b384e0..326bebeca301 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;
}