зеркало из https://github.com/mozilla/gecko-dev.git
723 строки
18 KiB
JavaScript
723 строки
18 KiB
JavaScript
/* 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;
|
|
|
|
// Emulate Promise.jsm semantics.
|
|
Promise.defer = function() { return new Deferred(); }
|
|
function Deferred() {
|
|
this.promise = new Promise(function(resolve, reject) {
|
|
this.resolve = resolve;
|
|
this.reject = reject;
|
|
}.bind(this));
|
|
Object.freeze(this);
|
|
}
|
|
|
|
/**
|
|
* Push a list of preference settings. Never reject.
|
|
*
|
|
* Fulfill params: (none)
|
|
*
|
|
* @param aPrefs
|
|
* An JS object. For example:
|
|
*
|
|
* {'set': [['foo.bar', 2], ['magic.pref', 'baz']],
|
|
* 'clear': [['clear.this'], ['also.this']] };
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function pushPrefEnv(aPrefs) {
|
|
let deferred = Promise.defer();
|
|
|
|
SpecialPowers.pushPrefEnv(aPrefs, function() {
|
|
ok(true, "preferences pushed: " + JSON.stringify(aPrefs));
|
|
deferred.resolve();
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Push required permissions and test if |navigator.mozMobileMessage| exists.
|
|
* Resolve if it does, reject otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* manager -- an reference to navigator.mozMobileMessage.
|
|
*
|
|
* Reject params: (none)
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
var manager;
|
|
function ensureMobileMessage() {
|
|
let deferred = Promise.defer();
|
|
|
|
let permissions = [{
|
|
"type": "sms",
|
|
"allow": 1,
|
|
"context": document,
|
|
}];
|
|
SpecialPowers.pushPermissions(permissions, function() {
|
|
ok(true, "permissions pushed: " + JSON.stringify(permissions));
|
|
|
|
manager = window.navigator.mozMobileMessage;
|
|
if (manager) {
|
|
log("navigator.mozMobileMessage is instance of " + manager.constructor);
|
|
} else {
|
|
log("navigator.mozMobileMessage is undefined.");
|
|
}
|
|
|
|
if (manager instanceof MozMobileMessageManager) {
|
|
deferred.resolve(manager);
|
|
} else {
|
|
deferred.reject();
|
|
}
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Push required permissions and test if |navigator.mozMobileConnections| exists.
|
|
* Resolve if it does, reject otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* manager -- an reference to navigator.mozMobileConnections.
|
|
*
|
|
* Reject params: (none)
|
|
*
|
|
* @param aServiceId [optional]
|
|
* A numeric DSDS service id. Default: 0.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
var mobileConnection;
|
|
function ensureMobileConnection(aServiceId) {
|
|
return new Promise(function(resolve, reject) {
|
|
let permissions = [{
|
|
"type": "mobileconnection",
|
|
"allow": 1,
|
|
"context": document,
|
|
}];
|
|
SpecialPowers.pushPermissions(permissions, function() {
|
|
ok(true, "permissions pushed: " + JSON.stringify(permissions));
|
|
|
|
let serviceId = aServiceId || 0;
|
|
mobileConnection = window.navigator.mozMobileConnections[serviceId];
|
|
if (mobileConnection) {
|
|
log("navigator.mozMobileConnections[" + serviceId + "] is instance of " +
|
|
mobileConnection.constructor);
|
|
} else {
|
|
log("navigator.mozMobileConnections[" + serviceId + "] is undefined");
|
|
}
|
|
|
|
if (mobileConnection instanceof MozMobileConnection) {
|
|
resolve(mobileConnection);
|
|
} else {
|
|
reject();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Wait for one named MobileMessageManager event.
|
|
*
|
|
* Resolve if that named event occurs. Never reject.
|
|
*
|
|
* Fulfill params: the DOMEvent passed.
|
|
*
|
|
* @param aEventName
|
|
* A string event name.
|
|
* @param aMatchFunc [optional]
|
|
* An additional callback function to match the interested event
|
|
* before removing the listener and going to resolve the promise.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function waitForManagerEvent(aEventName, aMatchFunc) {
|
|
let deferred = Promise.defer();
|
|
|
|
manager.addEventListener(aEventName, function onevent(aEvent) {
|
|
if (aMatchFunc && !aMatchFunc(aEvent)) {
|
|
ok(true, "MobileMessageManager event '" + aEventName + "' got" +
|
|
" but is not interested.");
|
|
return;
|
|
}
|
|
|
|
ok(true, "MobileMessageManager event '" + aEventName + "' got.");
|
|
manager.removeEventListener(aEventName, onevent);
|
|
deferred.resolve(aEvent);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Send a SMS message to a single receiver. Resolve if it succeeds, reject
|
|
* otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* message -- the sent SmsMessage.
|
|
*
|
|
* Reject params:
|
|
* error -- a DOMError.
|
|
*
|
|
* @param aReceiver the address of the receiver.
|
|
* @param aText the text body of the message.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendSmsWithSuccess(aReceiver, aText) {
|
|
return manager.send(aReceiver, aText);
|
|
}
|
|
|
|
/**
|
|
* Send a SMS message to a single receiver.
|
|
* Resolve if it fails, reject otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* {
|
|
* message, -- the failed MmsMessage
|
|
* error, -- error of the send request
|
|
* }
|
|
*
|
|
* Reject params: (none)
|
|
*
|
|
* @param aReceiver the address of the receiver.
|
|
* @param aText the text body of the message.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendSmsWithFailure(aReceiver, aText) {
|
|
let promises = [];
|
|
promises.push(waitForManagerEvent("failed")
|
|
.then((aEvent) => { return aEvent.message; }));
|
|
promises.push(manager.send(aReceiver, aText)
|
|
.then((aResult) => { throw aResult; },
|
|
(aError) => { return aError; }));
|
|
|
|
return Promise.all(promises)
|
|
.then((aResults) => { return { message: aResults[0],
|
|
error: aResults[1] }; });
|
|
}
|
|
|
|
/**
|
|
* Send a MMS message with specified parameters. Resolve if it fails, reject
|
|
* otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* {
|
|
* message, -- the failed MmsMessage
|
|
* error, -- error of the send request
|
|
* }
|
|
*
|
|
* Reject params: (none)
|
|
*
|
|
* @param aMmsParameters a MmsParameters instance.
|
|
*
|
|
* @param aSendParameters a MmsSendParameters instance.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendMmsWithFailure(aMmsParameters, aSendParameters) {
|
|
let promises = [];
|
|
promises.push(waitForManagerEvent("failed")
|
|
.then((aEvent) => { return aEvent.message; }));
|
|
promises.push(manager.sendMMS(aMmsParameters, aSendParameters)
|
|
.then((aResult) => { throw aResult; },
|
|
(aError) => { return aError; }));
|
|
|
|
return Promise.all(promises)
|
|
.then((aResults) => { return { message: aResults[0],
|
|
error: aResults[1] }; });
|
|
}
|
|
|
|
/**
|
|
* Retrieve message by message id.
|
|
*
|
|
* Fulfill params: SmsMessage
|
|
* Reject params:
|
|
* event -- a DOMEvent
|
|
*
|
|
* @param aId
|
|
* A numeric message id.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function getMessage(aId) {
|
|
return manager.getMessage(aId);
|
|
}
|
|
|
|
/**
|
|
* Retrieve messages from database.
|
|
*
|
|
* Fulfill params:
|
|
* messages -- an array of {Sms,Mms}Message instances.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent
|
|
*
|
|
* @param aFilter [optional]
|
|
* A MobileMessageFilter object.
|
|
* @param aReverse [optional]
|
|
* A boolean value indicating whether the order of the message should be
|
|
* reversed. Default: false.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function getMessages(aFilter, aReverse) {
|
|
let deferred = Promise.defer();
|
|
|
|
let messages = [];
|
|
let cursor = manager.getMessages(aFilter, aReverse || false);
|
|
cursor.onsuccess = function(aEvent) {
|
|
if (cursor.result) {
|
|
messages.push(cursor.result);
|
|
cursor.continue();
|
|
return;
|
|
}
|
|
|
|
deferred.resolve(messages);
|
|
};
|
|
cursor.onerror = deferred.reject.bind(deferred);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Retrieve all messages from database.
|
|
*
|
|
* Fulfill params:
|
|
* messages -- an array of {Sms,Mms}Message instances.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function getAllMessages() {
|
|
return getMessages(null, false);
|
|
}
|
|
|
|
/**
|
|
* Retrieve all threads from database.
|
|
*
|
|
* Fulfill params:
|
|
* threads -- an array of MozMobileMessageThread instances.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function getAllThreads() {
|
|
let deferred = Promise.defer();
|
|
|
|
let threads = [];
|
|
let cursor = manager.getThreads();
|
|
cursor.onsuccess = function(aEvent) {
|
|
if (cursor.result) {
|
|
threads.push(cursor.result);
|
|
cursor.continue();
|
|
return;
|
|
}
|
|
|
|
deferred.resolve(threads);
|
|
};
|
|
cursor.onerror = deferred.reject.bind(deferred);
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single specified thread from database.
|
|
*
|
|
* Fulfill params:
|
|
* thread -- a MozMobileMessageThread instance.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent if an error occurs in the retrieving process, or
|
|
* undefined if there's no such thread.
|
|
*
|
|
* @aThreadId a numeric value identifying the target thread.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function getThreadById(aThreadId) {
|
|
return getAllThreads()
|
|
.then(function(aThreads) {
|
|
for (let thread of aThreads) {
|
|
if (thread.id === aThreadId) {
|
|
return thread;
|
|
}
|
|
}
|
|
throw undefined;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete messages specified from database.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of boolean values indicating whether delesion was
|
|
* actually performed on the message record with corresponding id.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent.
|
|
*
|
|
* @aMessageId an array of numeric values identifying the target messages.
|
|
*
|
|
* @return An empty array if nothing to be deleted; otherwise, a deferred promise.
|
|
*/
|
|
function deleteMessagesById(aMessageIds) {
|
|
if (!aMessageIds.length) {
|
|
ok(true, "no message to be deleted");
|
|
return [];
|
|
}
|
|
|
|
let promises = [];
|
|
promises.push(waitForManagerEvent("deleted"));
|
|
promises.push(manager.delete(aMessageIds));
|
|
|
|
return Promise.all(promises)
|
|
.then((aResults) => {
|
|
return { deletedInfo: aResults[0],
|
|
deletedFlags: aResults[1] };
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete messages specified from database.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of boolean values indicating whether delesion was
|
|
* actually performed on the message record with corresponding id.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent.
|
|
*
|
|
* @aMessages an array of {Sms,Mms}Message instances.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function deleteMessages(aMessages) {
|
|
let ids = messagesToIds(aMessages);
|
|
return deleteMessagesById(ids);
|
|
}
|
|
|
|
/**
|
|
* Delete all messages from database.
|
|
*
|
|
* Fulfill params:
|
|
* ids -- an array of numeric values identifying those deleted
|
|
* {Sms,Mms}Messages.
|
|
*
|
|
* Reject params:
|
|
* event -- a DOMEvent.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function deleteAllMessages() {
|
|
return getAllMessages().then(deleteMessages);
|
|
}
|
|
|
|
var 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.
|
|
*
|
|
* @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;
|
|
}
|
|
|
|
/**
|
|
* Send simple text SMS to emulator.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @param aFrom
|
|
* A string-typed from address.
|
|
* @param aText
|
|
* A string-typed message body.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendTextSmsToEmulator(aFrom, aText) {
|
|
let command = "sms send " + aFrom + " " + aText;
|
|
return runEmulatorCmdSafe(command);
|
|
}
|
|
|
|
/**
|
|
* Send simple text SMS to emulator and wait for a received event.
|
|
*
|
|
* Fulfill params: SmsMessage
|
|
* Reject params: (none)
|
|
*
|
|
* @param aFrom
|
|
* A string-typed from address.
|
|
* @param aText
|
|
* A string-typed message body.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendTextSmsToEmulatorAndWait(aFrom, aText) {
|
|
let promises = [];
|
|
promises.push(waitForManagerEvent("received"));
|
|
promises.push(sendTextSmsToEmulator(aFrom, aText));
|
|
return Promise.all(promises).then(aResults => aResults[0].message);
|
|
}
|
|
|
|
/**
|
|
* Send raw SMS TPDU to emulator.
|
|
*
|
|
* @param: aPdu
|
|
* A hex string representing the whole SMS T-PDU.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendRawSmsToEmulator(aPdu) {
|
|
let command = "sms pdu " + aPdu;
|
|
return runEmulatorCmdSafe(command);
|
|
}
|
|
|
|
/**
|
|
* Send multiple raw SMS TPDU to emulator and wait
|
|
*
|
|
* @param: aPdus
|
|
* A array of hex strings. Each represents a SMS T-PDU.
|
|
*
|
|
* Fulfill params:
|
|
* result -- array of resolved Promise, where
|
|
* result[0].message representing the received message.
|
|
* result[1-n] represents the response of sent emulator command.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendMultipleRawSmsToEmulatorAndWait(aPdus) {
|
|
let promises = [];
|
|
|
|
promises.push(waitForManagerEvent("received"));
|
|
for (let pdu of aPdus) {
|
|
promises.push(sendRawSmsToEmulator(pdu));
|
|
}
|
|
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Set voice state and wait for state change.
|
|
*
|
|
* @param aState
|
|
* "unregistered", "searching", "denied", "roaming", or "home".
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function setEmulatorVoiceStateAndWait(aState) {
|
|
let promises = [];
|
|
promises.push(new Promise(function(resolve, reject) {
|
|
mobileConnection.addEventListener("voicechange", function onevent(aEvent) {
|
|
log("voicechange: connected=" + mobileConnection.voice.connected);
|
|
mobileConnection.removeEventListener("voicechange", onevent);
|
|
resolve(aEvent);
|
|
})
|
|
}));
|
|
|
|
promises.push(runEmulatorCmdSafe("gsm voice " + aState));
|
|
return Promise.all(promises);
|
|
}
|
|
|
|
/**
|
|
* Create a new array of id attribute of input messages.
|
|
*
|
|
* @param aMessages an array of {Sms,Mms}Message instances.
|
|
*
|
|
* @return an array of numeric values.
|
|
*/
|
|
function messagesToIds(aMessages) {
|
|
let ids = [];
|
|
for (let message of aMessages) {
|
|
ids.push(message.id);
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/**
|
|
* Convenient function to compare two SMS messages.
|
|
*/
|
|
function compareSmsMessage(aFrom, aTo) {
|
|
const FIELDS = ["id", "threadId", "iccId", "body", "delivery",
|
|
"deliveryStatus", "read", "receiver", "sender",
|
|
"messageClass", "timestamp", "deliveryTimestamp",
|
|
"sentTimestamp"];
|
|
|
|
for (let field of FIELDS) {
|
|
is(aFrom[field], aTo[field], "message." + field);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Wait for pending emulator transactions and call |finish()|.
|
|
*/
|
|
function cleanUp() {
|
|
ok(true, ":: CLEANING UP ::");
|
|
|
|
waitFor(finish, function() {
|
|
return pendingEmulatorCmdCount === 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
.then(cleanUp, function() {
|
|
ok(false, 'promise rejects during test.');
|
|
cleanUp();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 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()
|
|
.then(deleteAllMessages)
|
|
.then(aTestCaseMain)
|
|
.then(deleteAllMessages);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Helper to run the test case only needed in Multi-SIM environment.
|
|
*
|
|
* @param aTest
|
|
* A function which will be invoked w/o parameter.
|
|
* @return a Promise object.
|
|
*/
|
|
function runIfMultiSIM(aTest) {
|
|
let numRIL;
|
|
try {
|
|
numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces");
|
|
} catch (ex) {
|
|
numRIL = 1; // Pref not set.
|
|
}
|
|
|
|
if (numRIL > 1) {
|
|
return aTest();
|
|
} else {
|
|
log("Not a Multi-SIM environment. Test is skipped.");
|
|
return Promise.resolve();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Helper to enable/disable connection radio state.
|
|
*
|
|
* @param aConnection
|
|
* connection to enable / disable
|
|
* @param aEnabled
|
|
* True to enable the radio.
|
|
* @return a Promise object.
|
|
*/
|
|
function setRadioEnabled(aConnection, aEnabled) {
|
|
log("setRadioEnabled to " + aEnabled);
|
|
|
|
let deferred = Promise.defer();
|
|
let finalState = (aEnabled) ? "enabled" : "disabled";
|
|
if (aConnection.radioState == finalState) {
|
|
return deferred.resolve(aConnection);
|
|
}
|
|
|
|
aConnection.onradiostatechange = function() {
|
|
log("Received 'radiostatechange', radioState: " + aConnection.radioState);
|
|
|
|
if (aConnection.radioState == finalState) {
|
|
deferred.resolve(aConnection);
|
|
aConnection.onradiostatechange = null;
|
|
}
|
|
};
|
|
|
|
let req = aConnection.setRadioEnabled(aEnabled);
|
|
|
|
req.onsuccess = function() {
|
|
log("setRadioEnabled success");
|
|
};
|
|
|
|
req.onerror = function() {
|
|
ok(false, "setRadioEnabled should not fail");
|
|
deferred.reject();
|
|
};
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Helper to enable/disable all connections radio state.
|
|
*
|
|
* @param aEnabled
|
|
* True to enable the radio.
|
|
* @return a Promise object.
|
|
*/
|
|
function setAllRadioEnabled(aEnabled) {
|
|
let promises = []
|
|
for (let i = 0; i < window.navigator.mozMobileConnections.length; ++i) {
|
|
promises.push(ensureMobileConnection(i)
|
|
.then((connection) => setRadioEnabled(connection, aEnabled)));
|
|
}
|
|
return Promise.all(promises);
|
|
}
|