diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 14d2d071cf2a..fe293bb6e4e0 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 242f5c11cc40..b39c5e8a339b 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index d93d41600b3d..c57e9b88a604 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 14d2d071cf2a..fe293bb6e4e0 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index e043faa74b65..5b7cdb264724 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -4,6 +4,6 @@
"branch": "",
"revision": ""
},
- "revision": "c5bd933fe99317a7e99822f2d9345ae67a3043df",
+ "revision": "93de9c5bcb90ae32bd3599b185f4bbf67e4529ff",
"repo_path": "/integration/gaia-central"
}
diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml
index 8d741c0638ba..6eba97b45284 100644
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml
index 1693f0f93c75..1b28a400c2d0 100644
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -15,7 +15,7 @@
-
+
diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml
index 30f9065f1762..44d04d6ac832 100644
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -19,7 +19,7 @@
-
+
diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml
index 09de64da9f3b..d1fbcd541464 100644
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml
index d1da73c588bf..6deb2c5cbb71 100644
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml
index 75b35f1e6a9b..fc9275484961 100644
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -17,7 +17,7 @@
-
+
diff --git a/dom/bluetooth/tests/marionette/head.js b/dom/bluetooth/tests/marionette/head.js
index 2cbb53f97ec1..cb63a9e8df9e 100644
--- a/dom/bluetooth/tests/marionette/head.js
+++ b/dom/bluetooth/tests/marionette/head.js
@@ -9,12 +9,13 @@ let Promise =
let bluetoothManager;
-/* Get mozSettings value specified by @aKey.
+/**
+ * Get mozSettings value specified by @aKey.
*
* Resolve if that mozSettings value is retrieved successfully, reject
* otherwise.
*
- * Forfill params:
+ * Fulfill params:
* The corresponding mozSettings value of the key.
* Reject params: (none)
*
@@ -39,11 +40,12 @@ function getSettings(aKey) {
return deferred.promise;
}
-/* Set mozSettings values.
+/**
+ * Set mozSettings values.
*
* Resolve if that mozSettings value is set successfully, reject otherwise.
*
- * Forfill params: (none)
+ * Fulfill params: (none)
* Reject params: (none)
*
* @param aSettings
@@ -67,12 +69,13 @@ function setSettings(aSettings) {
return deferred.promise;
}
-/* Get mozSettings value of 'bluetooth.enabled'.
+/**
+ * Get mozSettings value of 'bluetooth.enabled'.
*
* Resolve if that mozSettings value is retrieved successfully, reject
* otherwise.
*
- * Forfill params:
+ * Fulfill params:
* A boolean value.
* Reject params: (none)
*
@@ -82,11 +85,12 @@ function getBluetoothEnabled() {
return getSettings("bluetooth.enabled");
}
-/* Set mozSettings value of 'bluetooth.enabled'.
+/**
+ * Set mozSettings value of 'bluetooth.enabled'.
*
* Resolve if that mozSettings value is set successfully, reject otherwise.
*
- * Forfill params: (none)
+ * Fulfill params: (none)
* Reject params: (none)
*
* @param aEnabled
@@ -100,10 +104,11 @@ function setBluetoothEnabled(aEnabled) {
return setSettings(obj);
}
-/* Push required permissions and test if |navigator.mozBluetooth| exists.
+/**
+ * Push required permissions and test if |navigator.mozBluetooth| exists.
* Resolve if it does, reject otherwise.
*
- * Forfill params:
+ * Fulfill params:
* bluetoothManager -- an reference to navigator.mozBluetooth.
* Reject params: (none)
*
@@ -151,11 +156,12 @@ function ensureBluetoothManager(aPermissions) {
return deferred.promise;
}
-/* Wait for one named BluetoothManager event.
+/**
+ * Wait for one named BluetoothManager event.
*
* Resolve if that named event occurs. Never reject.
*
- * Forfill params: the DOMEvent passed.
+ * Fulfill params: the DOMEvent passed.
*
* @return A deferred promise.
*/
@@ -172,12 +178,13 @@ function waitForManagerEvent(aEventName) {
return deferred.promise;
}
-/* Convenient function for setBluetoothEnabled and waitForManagerEvent
+/**
+ * Convenient function for setBluetoothEnabled and waitForManagerEvent
* combined.
*
* Resolve if that named event occurs. Reject if we can't set settings.
*
- * Forfill params: the DOMEvent passed.
+ * Fulfill params: the DOMEvent passed.
* Reject params: (none)
*
* @return A deferred promise.
@@ -198,11 +205,12 @@ function setBluetoothEnabledAndWait(aEnabled) {
return Promise.all(promises);
}
-/* Get default adapter.
+/**
+ * Get default adapter.
*
* Resolve if that default adapter is got, reject otherwise.
*
- * Forfill params: a BluetoothAdapter instance.
+ * Fulfill params: a BluetoothAdapter instance.
* Reject params: a DOMError, or null if if there is no adapter ready yet.
*
* @return A deferred promise.
@@ -237,7 +245,8 @@ function getDefaultAdapter() {
return deferred.promise;
}
-/* Flush permission settings and call |finish()|.
+/**
+ * Flush permission settings and call |finish()|.
*/
function cleanUp() {
SpecialPowers.flushPermissions(function() {
diff --git a/dom/mobileconnection/tests/marionette/head.js b/dom/mobileconnection/tests/marionette/head.js
new file mode 100644
index 000000000000..623d8047903a
--- /dev/null
+++ b/dom/mobileconnection/tests/marionette/head.js
@@ -0,0 +1,399 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
+
+const SETTINGS_KEY_DATA_ENABLED = "ril.data.enabled";
+const SETTINGS_KEY_DATA_ROAMING_ENABLED = "ril.data.roaming_enabled";
+const SETTINGS_KEY_DATA_APN_SETTINGS = "ril.data.apnSettings";
+
+let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
+
+let _pendingEmulatorCmdCount = 0;
+
+/**
+ * Send emulator command with safe guard.
+ *
+ * We should only call |finish()| after all emulator command transactions
+ * end, so here comes with the pending counter. Resolve when the emulator
+ * gives positive response, and reject otherwise.
+ *
+ * Fulfill params:
+ * result -- an array of emulator response lines.
+ * Reject params:
+ * result -- an array of emulator response lines.
+ *
+ * @param aCommand
+ * A string command to be passed to emulator through its telnet console.
+ *
+ * @return A deferred promise.
+ */
+function runEmulatorCmdSafe(aCommand) {
+ let deferred = Promise.defer();
+
+ ++_pendingEmulatorCmdCount;
+ runEmulatorCmd(aCommand, function(aResult) {
+ --_pendingEmulatorCmdCount;
+
+ ok(true, "Emulator response: " + JSON.stringify(aResult));
+ if (Array.isArray(aResult) && aResult[0] === "OK") {
+ deferred.resolve(aResult);
+ } else {
+ deferred.reject(aResult);
+ }
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Get mozSettings value specified by @aKey.
+ *
+ * Resolve if that mozSettings value is retrieved successfully, reject
+ * otherwise.
+ *
+ * Fulfill params:
+ * The corresponding mozSettings value of the key.
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+function getSettings(aKey, aAllowError) {
+ let deferred = Promise.defer();
+
+ let request = navigator.mozSettings.createLock().get(aKey);
+ request.addEventListener("success", function(aEvent) {
+ ok(true, "getSettings(" + aKey + ") - success");
+ deferred.resolve(aEvent.target.result[aKey]);
+ });
+ request.addEventListener("error", function() {
+ ok(aAllowError, "getSettings(" + aKey + ") - error");
+ deferred.reject();
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Set mozSettings values.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aSettings
+ * An object of format |{key1: value1, key2: value2, ...}|.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+function setSettings(aSettings, aAllowError) {
+ let deferred = Promise.defer();
+
+ let request = navigator.mozSettings.createLock().set(aSettings);
+ request.addEventListener("success", function() {
+ ok(true, "setSettings(" + JSON.stringify(aSettings) + ")");
+ deferred.resolve();
+ });
+ request.addEventListener("error", function() {
+ ok(aAllowError, "setSettings(" + JSON.stringify(aSettings) + ")");
+ deferred.reject();
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Set mozSettings value with only one key.
+ *
+ * Resolve if that mozSettings value is set successfully, reject otherwise.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aKey
+ * A string key.
+ * @param aValue
+ * An object value.
+ * @param aAllowError [optional]
+ * A boolean value. If set to true, an error response won't be treated
+ * as test failure. Default: false.
+ *
+ * @return A deferred promise.
+ */
+function setSettings1(aKey, aValue, aAllowError) {
+ let settings = {};
+ settings[aKey] = aValue;
+ return setSettings(settings, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_ENABLED.
+ */
+function getDataEnabled(aAllowError) {
+ return getSettings(SETTINGS_KEY_DATA_ENABLED, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_ENABLED.
+ */
+function setDataEnabled(aEnabled, aAllowError) {
+ return setSettings1(SETTINGS_KEY_DATA_ENABLED, aEnabled, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
+ */
+function getDataRoamingEnabled(aAllowError) {
+ return getSettings(SETTINGS_KEY_DATA_ROAMING_ENABLED, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_ROAMING_ENABLED.
+ */
+function setDataRoamingEnabled(aEnabled, aAllowError) {
+ return setSettings1(SETTINGS_KEY_DATA_ROAMING_ENABLED, aEnabled, aAllowError);
+}
+
+/**
+ * Convenient MozSettings getter for SETTINGS_KEY_DATA_APN_SETTINGS.
+ */
+function getDataApnSettings(aAllowError) {
+ return getSettings(SETTINGS_KEY_DATA_APN_SETTINGS, aAllowError);
+}
+
+/**
+ * Convenient MozSettings setter for SETTINGS_KEY_DATA_APN_SETTINGS.
+ */
+function setDataApnSettings(aApnSettings, aAllowError) {
+ return setSettings1(SETTINGS_KEY_DATA_APN_SETTINGS, aApnSettings, aAllowError);
+}
+
+let mobileConnection;
+
+/**
+ * Push required permissions and test if
+ * |navigator.mozMobileConnections[]| exists. Resolve if it does,
+ * reject otherwise.
+ *
+ * Fulfill params:
+ * mobileConnection -- an reference to navigator.mozMobileMessage.
+ *
+ * Reject params: (none)
+ *
+ * @param aAdditonalPermissions [optional]
+ * An array of permission strings other than "mobileconnection" to be
+ * pushed. Default: empty string.
+ * @param aServiceId [optional]
+ * A numeric DSDS service id. Default: 0.
+ *
+ * @return A deferred promise.
+ */
+function ensureMobileConnection(aAdditionalPermissions, aServiceId) {
+ let deferred = Promise.defer();
+
+ aAdditionalPermissions = aAdditionalPermissions || [];
+ aServiceId = aServiceId || 0;
+
+ if (aAdditionalPermissions.indexOf("mobileconnection") < 0) {
+ aAdditionalPermissions.push("mobileconnection");
+ }
+ let permissions = [];
+ for (let perm of aAdditionalPermissions) {
+ permissions.push({ "type": perm, "allow": 1, "context": document });
+ }
+
+ SpecialPowers.pushPermissions(permissions, function() {
+ ok(true, "permissions pushed: " + JSON.stringify(permissions));
+
+ // Permission changes can't change existing Navigator.prototype
+ // objects, so grab our objects from a new Navigator.
+ let ifr = document.createElement("iframe");
+ ifr.addEventListener("load", function load() {
+ ifr.removeEventListener("load", load);
+
+ mobileConnection =
+ ifr.contentWindow.navigator.mozMobileConnections[aServiceId];
+
+ if (mobileConnection) {
+ log("navigator.mozMobileConnections[" + aServiceId + "] is instance of " +
+ mobileConnection.constructor);
+ } else {
+ log("navigator.mozMobileConnections[" + aServiceId + "] is undefined");
+ }
+
+ if (mobileConnection instanceof MozMobileConnection) {
+ deferred.resolve(mobileConnection);
+ } else {
+ deferred.reject();
+ }
+ });
+
+ document.body.appendChild(ifr);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Wait for one named MobileConnection event.
+ *
+ * Resolve if that named event occurs. Never reject.
+ *
+ * Fulfill params: the DOMEvent passed.
+ *
+ * @param aEventName
+ * A string event name.
+ *
+ * @return A deferred promise.
+ */
+function waitForManagerEvent(aEventName) {
+ let deferred = Promise.defer();
+
+ mobileConnection.addEventListener(aEventName, function onevent(aEvent) {
+ mobileConnection.removeEventListener(aEventName, onevent);
+
+ ok(true, "MobileConnection event '" + aEventName + "' got.");
+ deferred.resolve(aEvent);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Set data connection enabling state and wait for "datachange" event.
+ *
+ * Resolve if data connection state changed to the expected one. Never reject.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aEnabled
+ * A boolean state.
+ *
+ * @return A deferred promise.
+ */
+function setDataEnabledAndWait(aEnabled) {
+ let deferred = Promise.defer();
+
+ let promises = [];
+ promises.push(waitForManagerEvent("datachange"));
+ promises.push(setDataEnabled(aEnabled));
+ Promise.all(promises).then(function keepWaiting() {
+ // To ignore some transient states, we only resolve that deferred promise
+ // when the |connected| state equals to the expected one and never rejects.
+ let connected = mobileConnection.data.connected;
+ if (connected == aEnabled) {
+ deferred.resolve();
+ return;
+ }
+
+ return waitForManagerEvent("datachange").then(keepWaiting);
+ });
+
+ return deferred.promise;
+}
+
+/**
+ * Set voice/data roaming emulation and wait for state change.
+ *
+ * Fulfill params: (none)
+ *
+ * @param aRoaming
+ * A boolean state.
+ *
+ * @return A deferred promise.
+ */
+function setEmulatorRoamingAndWait(aRoaming) {
+ function doSetAndWait(aWhich, aRoaming) {
+ let promises = [];
+ promises.push(waitForManagerEvent(aWhich + "change"));
+
+ let cmd = "gsm " + aWhich + " " + (aRoaming ? "roaming" : "home");
+ promises.push(runEmulatorCmdSafe(cmd));
+ return Promise.all(promises)
+ .then(() => is(mobileConnection[aWhich].roaming, aRoaming,
+ aWhich + ".roaming"));
+ }
+
+ // Set voice registration state first and then data registration state.
+ return doSetAndWait("voice", aRoaming)
+ .then(() => doSetAndWait("data", aRoaming));
+}
+
+let _networkManager;
+
+/**
+ * Get internal NetworkManager service.
+ */
+function getNetworkManager() {
+ if (!_networkManager) {
+ _networkManager = Cc["@mozilla.org/network/manager;1"]
+ .getService(Ci.nsINetworkManager);
+ ok(_networkManager, "NetworkManager");
+ }
+
+ return _networkManager;
+}
+
+/**
+ * Flush permission settings and call |finish()|.
+ */
+function cleanUp() {
+ waitFor(function() {
+ SpecialPowers.flushPermissions(function() {
+ // Use ok here so that we have at least one test run.
+ ok(true, "permissions flushed");
+
+ finish();
+ });
+ }, function() {
+ return _pendingEmulatorCmdCount === 0;
+ });
+}
+
+/**
+ * Basic test routine helper for mobile connection tests.
+ *
+ * This helper does nothing but clean-ups.
+ *
+ * @param aTestCaseMain
+ * A function that takes no parameter.
+ */
+function startTestBase(aTestCaseMain) {
+ Promise.resolve()
+ .then(aTestCaseMain)
+ .then(cleanUp, function() {
+ ok(false, 'promise rejects during test.');
+ cleanUp();
+ });
+}
+
+/**
+ * Common test routine helper for mobile connection tests.
+ *
+ * This function ensures global |mobileConnection| variable is available during
+ * the process and performs clean-ups as well.
+ *
+ * @param aTestCaseMain
+ * A function that takes one parameter -- mobileConnection.
+ * @param aAdditonalPermissions [optional]
+ * An array of permission strings other than "mobileconnection" to be
+ * pushed. Default: empty string.
+ * @param aServiceId [optional]
+ * A numeric DSDS service id. Default: 0.
+ */
+function startTestCommon(aTestCaseMain, aAdditionalPermissions, aServiceId) {
+ startTestBase(function() {
+ return ensureMobileConnection(aAdditionalPermissions, aServiceId)
+ .then(aTestCaseMain);
+ });
+}
diff --git a/dom/mobileconnection/tests/marionette/manifest.ini b/dom/mobileconnection/tests/marionette/manifest.ini
index 88134a44c139..07ad02777d69 100644
--- a/dom/mobileconnection/tests/marionette/manifest.ini
+++ b/dom/mobileconnection/tests/marionette/manifest.ini
@@ -23,3 +23,5 @@ disabled = Bug 808783
[test_mobile_last_known_network.js]
[test_mobile_icc_change.js]
[test_mobile_connections_array_uninitialized.js]
+[test_mobile_data_ipv6.js]
+disabled = Bug 978071
diff --git a/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js
new file mode 100644
index 000000000000..bf83dfa48c50
--- /dev/null
+++ b/dom/mobileconnection/tests/marionette/test_mobile_data_ipv6.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = "head.js";
+
+/**
+ * Test resulting IP address format with given APN settings.
+ *
+ * This test utility function performs following steps:
+ *
+ * 1) set "ril.data.apnSettings" to a given settings object,
+ * 2) enable data connection and wait for a "datachange" event,
+ * 3) check the IP address type of the active network interface,
+ * 4) disable data connection.
+ *
+ * Fulfill params: (none)
+ * Reject params: (none)
+ *
+ * @param aApnSettings
+ * An APN settings value.
+ * @param aIsIPv6
+ * A boolean value indicating whether we're expecting an IPv6 address.
+ *
+ * @return A deferred promise.
+ */
+function doTest(aApnSettings, aIsIPv6) {
+ return setDataApnSettings([])
+ .then(() => setDataApnSettings(aApnSettings))
+ .then(() => setDataEnabledAndWait(true))
+ .then(function() {
+ let nm = getNetworkManager();
+ let active = nm.active;
+ ok(active, "Active network interface");
+
+ log(" Interface: " + active.name);
+ log(" Address: " + active.ip);
+ if (aIsIPv6) {
+ ok(active.ip.indexOf(":") > 0, "IPv6 address");
+ } else {
+ ok(active.ip.indexOf(":") < 0, "IPv4 address");
+ }
+ })
+ .then(() => setDataEnabledAndWait(false));
+}
+
+function doTestHome(aApnSettings, aProtocol) {
+ log("Testing \"" + aProtocol + "\"@HOME... ");
+
+ // aApnSettings is a double-array of per PDP context settings. The first
+ // index is a DSDS service ID, and the second one is the index of pre-defined
+ // PDP context settings of a specified radio interface. We use 0 for both as
+ // default here.
+ aApnSettings[0][0].protocol = aProtocol;
+ delete aApnSettings[0][0].roaming_protocol;
+
+ return doTest(aApnSettings, aProtocol === "IPV6");
+}
+
+function doTestRoaming(aApnSettings, aRoaminProtocol) {
+ log("Testing \"" + aRoaminProtocol + "\"@ROMAING... ");
+
+ // aApnSettings is a double-array of per PDP context settings. The first
+ // index is a DSDS service ID, and the second one is the index of pre-defined
+ // PDP context settings of a specified radio interface. We use 0 for both as
+ // default here.
+ delete aApnSettings[0][0].protocol;
+ aApnSettings[0][0].roaming_protocol = aRoaminProtocol;
+
+ return doTest(aApnSettings, aRoaminProtocol === "IPV6");
+}
+
+startTestCommon(function() {
+ let origApnSettings;
+
+ return setDataRoamingEnabled(true)
+ .then(getDataApnSettings)
+ .then(function(aResult) {
+ // If already set, then save original APN settings.
+ origApnSettings = JSON.parse(JSON.stringify(aResult));
+ return aResult;
+ }, function() {
+ // Return our own default value.
+ return [[{ "carrier": "T-Mobile US",
+ "apn": "epc.tmobile.com",
+ "mmsc": "http://mms.msg.eng.t-mobile.com/mms/wapenc",
+ "types": ["default", "supl", "mms"] }]];
+ })
+
+ .then(function(aApnSettings) {
+ return Promise.resolve()
+
+ .then(() => doTestHome(aApnSettings, "NoSuchProtocol"))
+ .then(() => doTestHome(aApnSettings, "IP"))
+ .then(() => doTestHome(aApnSettings, "IPV6"))
+
+ .then(() => setEmulatorRoamingAndWait(true))
+
+ .then(() => doTestRoaming(aApnSettings, "NoSuchProtocol"))
+ .then(() => doTestRoaming(aApnSettings, "IP"))
+ .then(() => doTestRoaming(aApnSettings, "IPV6"))
+
+ .then(() => setEmulatorRoamingAndWait(false));
+ })
+
+ .then(() => setDataRoamingEnabled(false))
+ .then(function() {
+ if (origApnSettings) {
+ return setDataApnSettings(origApnSettings);
+ }
+ });
+}, ["settings-read", "settings-write"]);
diff --git a/dom/mobilemessage/tests/marionette/head.js b/dom/mobilemessage/tests/marionette/head.js
index fd2572e36074..2c9bba498f73 100644
--- a/dom/mobilemessage/tests/marionette/head.js
+++ b/dom/mobilemessage/tests/marionette/head.js
@@ -5,7 +5,8 @@ const {Cc: Cc, Ci: Ci, Cr: Cr, Cu: Cu} = SpecialPowers;
let Promise = Cu.import("resource://gre/modules/Promise.jsm").Promise;
-/* Push required permissions and test if |navigator.mozMobileMessage| exists.
+/**
+ * Push required permissions and test if |navigator.mozMobileMessage| exists.
* Resolve if it does, reject otherwise.
*
* Fulfill params:
@@ -44,7 +45,8 @@ function ensureMobileMessage() {
return deferred.promise;
}
-/* Send a SMS message to a single receiver. Resolve if it succeeds, reject
+/**
+ * Send a SMS message to a single receiver. Resolve if it succeeds, reject
* otherwise.
*
* Fulfill params:
@@ -72,7 +74,8 @@ function sendSmsWithSuccess(aReceiver, aText) {
return deferred.promise;
}
-/* Send a MMS message with specified parameters. Resolve if it fails, reject
+/**
+ * Send a MMS message with specified parameters. Resolve if it fails, reject
* otherwise.
*
* Fulfill params:
@@ -100,7 +103,8 @@ function sendMmsWithFailure(aMmsParameters) {
return deferred.promise;
}
-/* Retrieve messages from database.
+/**
+ * Retrieve messages from database.
*
* Fulfill params:
* messages -- an array of {Sms,Mms}Message instances.
@@ -136,7 +140,8 @@ function getMessages(aFilter, aReverse) {
return deferred.promise;
}
-/* Retrieve all messages from database.
+/**
+ * Retrieve all messages from database.
*
* Fulfill params:
* messages -- an array of {Sms,Mms}Message instances.
@@ -150,7 +155,8 @@ function getAllMessages() {
return getMessages(null, false);
}
-/* Retrieve all threads from database.
+/**
+ * Retrieve all threads from database.
*
* Fulfill params:
* threads -- an array of MozMobileMessageThread instances.
@@ -179,7 +185,8 @@ function getAllThreads() {
return deferred.promise;
}
-/* Retrieve a single specified thread from database.
+/**
+ * Retrieve a single specified thread from database.
*
* Fulfill params:
* thread -- a MozMobileMessageThread instance.
@@ -204,7 +211,8 @@ function getThreadById(aThreadId) {
});
}
-/* Delete messages specified from database.
+/**
+ * Delete messages specified from database.
*
* Fulfill params:
* result -- an array of boolean values indicating whether delesion was
@@ -234,7 +242,8 @@ function deleteMessagesById(aMessageIds) {
return deferred.promise;
}
-/* Delete messages specified from database.
+/**
+ * Delete messages specified from database.
*
* Fulfill params:
* result -- an array of boolean values indicating whether delesion was
@@ -252,7 +261,8 @@ function deleteMessages(aMessages) {
return deleteMessagesById(ids);
}
-/* Delete all messages from database.
+/**
+ * Delete all messages from database.
*
* Fulfill params:
* ids -- an array of numeric values identifying those deleted
@@ -269,7 +279,8 @@ function deleteAllMessages() {
let pendingEmulatorCmdCount = 0;
-/* Send emulator command with safe guard.
+/**
+ * Send emulator command with safe guard.
*
* We should only call |finish()| after all emulator command transactions
* end, so here comes with the pending counter. Resolve when the emulator
@@ -301,7 +312,8 @@ function runEmulatorCmdSafe(aCommand) {
return deferred.promise;
}
-/* Send simple text SMS to emulator.
+/**
+ * Send simple text SMS to emulator.
*
* Fulfill params:
* result -- an array of emulator response lines.
@@ -316,7 +328,8 @@ function sendTextSmsToEmulator(aFrom, aText) {
return runEmulatorCmdSafe(command);
}
-/* Send raw SMS TPDU to emulator.
+/**
+ * Send raw SMS TPDU to emulator.
*
* Fulfill params:
* result -- an array of emulator response lines.
@@ -331,7 +344,8 @@ function sendRawSmsToEmulator(aPdu) {
return runEmulatorCmdSafe(command);
}
-/* Name space for MobileMessageDB.jsm. Only initialized after first call to
+/**
+ * Name space for MobileMessageDB.jsm. Only initialized after first call to
* newMobileMessageDB.
*/
let MMDB;
@@ -348,7 +362,8 @@ function newMobileMessageDB() {
return mmdb;
}
-/* Initialize a MobileMessageDB. Resolve if initialized with success, reject
+/**
+ * Initialize a MobileMessageDB. Resolve if initialized with success, reject
* otherwise.
*
* Fulfill params: a MobileMessageDB instance.
@@ -378,7 +393,8 @@ function initMobileMessageDB(aMmdb, aDbName, aDbVersion) {
return deferred.promise;
}
-/* Close a MobileMessageDB.
+/**
+ * Close a MobileMessageDB.
*
* @return The passed MobileMessageDB instance.
*/
@@ -387,7 +403,8 @@ function closeMobileMessageDB(aMmdb) {
return aMmdb;
}
-/* Create a new array of id attribute of input messages.
+/**
+ * Create a new array of id attribute of input messages.
*
* @param aMessages an array of {Sms,Mms}Message instances.
*
@@ -404,7 +421,8 @@ function messagesToIds(aMessages) {
// A reference to a nsIUUIDGenerator service.
let uuidGenerator;
-/* Generate a new UUID.
+/**
+ * Generate a new UUID.
*
* @return A UUID string.
*/
@@ -418,7 +436,8 @@ function newUUID() {
return uuidGenerator.generateUUID().toString();
}
-/* Flush permission settings and call |finish()|.
+/**
+ * Flush permission settings and call |finish()|.
*/
function cleanUp() {
waitFor(function() {
@@ -433,6 +452,14 @@ function cleanUp() {
});
}
+/**
+ * Basic test routine helper for mobile message tests.
+ *
+ * This helper does nothing but clean-ups.
+ *
+ * @param aTestCaseMain
+ * A function that takes no parameter.
+ */
function startTestBase(aTestCaseMain) {
Promise.resolve()
.then(aTestCaseMain)
@@ -442,6 +469,15 @@ function startTestBase(aTestCaseMain) {
});
}
+/**
+ * Common test routine helper for mobile message tests.
+ *
+ * This function ensures global |manager| variable is available during the
+ * process and performs clean-ups as well.
+ *
+ * @param aTestCaseMain
+ * A function that takes no parameter.
+ */
function startTestCommon(aTestCaseMain) {
startTestBase(function() {
return ensureMobileMessage()
diff --git a/dom/system/gonk/RadioInterfaceLayer.js b/dom/system/gonk/RadioInterfaceLayer.js
index b09a0a64533e..5150c22304c0 100644
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -52,6 +52,10 @@ let RILQUIRKS_DATA_REGISTRATION_ON_DEMAND =
let RILQUIRKS_RADIO_OFF_WO_CARD =
libcutils.property_get("ro.moz.ril.radio_off_wo_card", "false") == "true";
+// Ril quirk to enable IPv6 protocol/roaming protocol in APN settings.
+let RILQUIRKS_HAVE_IPV6 =
+ libcutils.property_get("ro.moz.ril.ipv6", "false") == "true";
+
const RADIOINTERFACELAYER_CID =
Components.ID("{2d831c8d-6017-435b-a80c-e5d422810cea}");
const RADIOINTERFACE_CID =
@@ -4525,12 +4529,25 @@ RILNetworkInterface.prototype = {
}
authType = RIL.RIL_DATACALL_AUTH_TO_GECKO.indexOf(RIL.GECKO_DATACALL_AUTH_DEFAULT);
}
+ let pdpType = RIL.GECKO_DATACALL_PDP_TYPE_IP;
+ if (RILQUIRKS_HAVE_IPV6) {
+ pdpType = !radioInterface.rilContext.data.roaming
+ ? this.apnSetting.protocol
+ : this.apnSetting.roaming_protocol;
+ if (RIL.RIL_DATACALL_PDP_TYPES.indexOf(pdpType) < 0) {
+ if (DEBUG) {
+ this.debug("Invalid pdpType '" + pdpType + "', using '" +
+ RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT + "'");
+ }
+ pdpType = RIL.GECKO_DATACALL_PDP_TYPE_DEFAULT;
+ }
+ }
radioInterface.setupDataCall(radioTechnology,
this.apnSetting.apn,
this.apnSetting.user,
this.apnSetting.password,
authType,
- "IP");
+ pdpType);
this.connecting = true;
},
diff --git a/dom/system/gonk/ril_consts.js b/dom/system/gonk/ril_consts.js
index 81b44114a05b..f0d070ed2ce6 100644
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -2361,6 +2361,15 @@ this.RIL_DATACALL_AUTH_TO_GECKO = [
GECKO_DATACALL_AUTH_PAP_OR_CHAP // DATACALL_AUTH_PAP_OR_CHAP
];
+this.GECKO_DATACALL_PDP_TYPE_IP = "IP";
+this.GECKO_DATACALL_PDP_TYPE_IPV6 = "IPV6";
+this.GECKO_DATACALL_PDP_TYPE_DEFAULT = GECKO_DATACALL_PDP_TYPE_IP;
+this.RIL_DATACALL_PDP_TYPES = [
+ GECKO_DATACALL_PDP_TYPE_IP,
+ GECKO_DATACALL_PDP_TYPE_IPV6,
+ // TODO: Bug 978711 - Support IPV4V6
+];
+
this.DATACALL_PROFILE_DEFAULT = 0;
this.DATACALL_PROFILE_TETHERED = 1;
this.DATACALL_PROFILE_OEM_BASE = 1000;
diff --git a/dom/system/gonk/ril_worker.js b/dom/system/gonk/ril_worker.js
index 94024ef52fdc..cbe07ee855e3 100644
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -3962,8 +3962,9 @@ RilObject.prototype = {
}
currentDataCall.gw = updatedDataCall.gw;
if (updatedDataCall.dns) {
- currentDataCall.dns[0] = updatedDataCall.dns[0];
- currentDataCall.dns[1] = updatedDataCall.dns[1];
+ currentDataCall.dns = updatedDataCall.dns.slice();
+ } else {
+ currentDataCall.dns = [];
}
currentDataCall.rilMessageType = "datacallstatechange";
this.sendChromeMessage(currentDataCall);
diff --git a/testing/xpcshell/mach_commands.py b/testing/xpcshell/mach_commands.py
index 0ebc0105f200..0d56f615faf8 100644
--- a/testing/xpcshell/mach_commands.py
+++ b/testing/xpcshell/mach_commands.py
@@ -11,6 +11,7 @@ import logging
import os
import shutil
import sys
+import urllib2
from StringIO import StringIO
@@ -26,6 +27,15 @@ from mach.decorators import (
Command,
)
+ADB_NOT_FOUND = '''
+The %s command requires the adb binary to be on your path.
+
+If you have a B2G build, this can be found in
+'%s/out/host//bin'.
+'''.lstrip()
+
+BUSYBOX_URL = 'http://www.busybox.net/downloads/binaries/latest/busybox-armv7l'
+
if sys.version_info[0] < 3:
unicode_type = unicode
@@ -274,9 +284,99 @@ class AndroidXPCShellRunner(MozbuildObject):
return int(not result)
+class B2GXPCShellRunner(MozbuildObject):
+ def __init__(self, *args, **kwargs):
+ MozbuildObject.__init__(self, *args, **kwargs)
+
+ # TODO Bug 794506 remove once mach integrates with virtualenv.
+ build_path = os.path.join(self.topobjdir, 'build')
+ if build_path not in sys.path:
+ sys.path.append(build_path)
+
+ build_path = os.path.join(self.topsrcdir, 'build')
+ if build_path not in sys.path:
+ sys.path.append(build_path)
+
+ self.tests_dir = os.path.join(self.topobjdir, '_tests')
+ self.xpcshell_dir = os.path.join(self.tests_dir, 'xpcshell')
+ self.bin_dir = os.path.join(self.distdir, 'bin')
+
+ def _download_busybox(self, b2g_home):
+ system_bin = os.path.join(b2g_home, 'out', 'target', 'product', 'generic', 'system', 'bin')
+ busybox_path = os.path.join(system_bin, 'busybox')
+
+ if os.path.isfile(busybox_path):
+ return busybox_path
+
+ if not os.path.isdir(system_bin):
+ os.makedirs(system_bin)
+
+ try:
+ data = urllib2.urlopen(BUSYBOX_URL)
+ except urllib2.URLError:
+ print('There was a problem downloading busybox. Proceeding without it,' \
+ 'initial setup will be slow.')
+ return
+
+ with open(busybox_path, 'wb') as f:
+ f.write(data.read())
+ return busybox_path
+
+ def run_test(self, test_file, b2g_home=None, busybox=None,
+ # ignore parameters from other platforms' options
+ **kwargs):
+ try:
+ import which
+ which.which('adb')
+ except which.WhichError:
+ # TODO Find adb automatically if it isn't on the path
+ print(ADB_NOT_FOUND % ('mochitest-remote', b2g_home))
+ sys.exit(1)
+
+ test_path = None
+ if test_file:
+ test_path = self._wrap_path_argument(test_file).relpath()
+
+ import runtestsb2g
+ parser = runtestsb2g.B2GOptions()
+ options, args = parser.parse_args([])
+
+ options.b2g_path = b2g_home
+ options.busybox = busybox or os.environ.get('BUSYBOX')
+ options.emulator = 'arm'
+ options.localLib = self.bin_dir
+ options.localBin = self.bin_dir
+ options.logcat_dir = self.xpcshell_dir
+ options.manifest = os.path.join(self.xpcshell_dir, 'xpcshell_b2g.ini')
+ options.mozInfo = os.path.join(self.topobjdir, 'mozinfo.json')
+ options.objdir = self.topobjdir
+ options.symbolsPath = os.path.join(self.distdir, 'crashreporter-symbols'),
+ options.testingModulesDir = os.path.join(self.tests_dir, 'modules')
+ options.testsRootDir = self.xpcshell_dir
+ options.testPath = test_path
+ options.use_device_libs = True
+
+ if not options.busybox:
+ options.busybox = self._download_busybox(b2g_home)
+
+ return runtestsb2g.run_remote_xpcshell(parser, options, args)
+
+def is_platform_supported(cls):
+ """Must have a Firefox, Android or B2G build."""
+ return conditions.is_android(cls) or \
+ conditions.is_b2g(cls) or \
+ conditions.is_firefox(cls)
+
@CommandProvider
class MachCommands(MachCommandBase):
+ def __init__(self, context):
+ MachCommandBase.__init__(self, context)
+
+ for attr in ('b2g_home', 'device_name'):
+ setattr(self, attr, getattr(context, attr, None))
+
@Command('xpcshell-test', category='testing',
+ conditions=[is_platform_supported],
description='Run XPCOM Shell tests.')
@CommandArgument('test_file', default='all', nargs='?', metavar='TEST',
help='Test to run. Can be specified as a single JS file, a directory, '
@@ -313,6 +413,8 @@ class MachCommands(MachCommandBase):
help='(Android) Do not copy files to device')
@CommandArgument('--local-apk', type=str, default=None,
help='(Android) Use specified Fennec APK')
+ @CommandArgument('--busybox', type=str, default=None,
+ help='(B2G) Path to busybox binary (speeds up installation of tests).')
def run_xpcshell_test(self, **params):
from mozbuild.controller.building import BuildDriver
@@ -326,6 +428,9 @@ class MachCommands(MachCommandBase):
if conditions.is_android(self):
xpcshell = self._spawn(AndroidXPCShellRunner)
+ elif conditions.is_b2g(self):
+ xpcshell = self._spawn(B2GXPCShellRunner)
+ params['b2g_home'] = self.b2g_home
else:
xpcshell = self._spawn(XPCShellRunner)
xpcshell.cwd = self._mach_context.cwd
diff --git a/testing/xpcshell/runtestsb2g.py b/testing/xpcshell/runtestsb2g.py
index 55e417b70b02..872982d3ae7c 100644
--- a/testing/xpcshell/runtestsb2g.py
+++ b/testing/xpcshell/runtestsb2g.py
@@ -153,9 +153,7 @@ class B2GOptions(RemoteXPCShellOptions):
self.error("You must specify --emulator if you specify --logcat-dir")
return RemoteXPCShellOptions.verifyRemoteOptions(self, options)
-def main():
- parser = B2GOptions()
- options, args = parser.parse_args()
+def run_remote_xpcshell(parser, options, args):
options = parser.verifyRemoteOptions(options)
# Create the Marionette instance
@@ -212,6 +210,11 @@ def main():
traceback.print_exc()
sys.exit(1)
+def main():
+ parser = B2GOptions()
+ options, args = parser.parse_args()
+
+ run_remote_xpcshell(parser, options, args)
# You usually run this like :
# python runtestsb2g.py --emulator arm --b2gpath $B2GPATH --manifest $MANIFEST [--xre-path $MOZ_HOST_BIN