mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-09-11 02:56:06 +00:00
412 lines
12 KiB
JavaScript
412 lines
12 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);
|
|
}
|
|
|
|
const PDU_DCS_CODING_GROUP_BITS = 0xF0;
|
|
const PDU_DCS_MSG_CODING_7BITS_ALPHABET = 0x00;
|
|
const PDU_DCS_MSG_CODING_8BITS_ALPHABET = 0x04;
|
|
const PDU_DCS_MSG_CODING_16BITS_ALPHABET = 0x08;
|
|
|
|
const PDU_DCS_MSG_CLASS_BITS = 0x03;
|
|
const PDU_DCS_MSG_CLASS_NORMAL = 0xFF;
|
|
const PDU_DCS_MSG_CLASS_0 = 0x00;
|
|
const PDU_DCS_MSG_CLASS_ME_SPECIFIC = 0x01;
|
|
const PDU_DCS_MSG_CLASS_SIM_SPECIFIC = 0x02;
|
|
const PDU_DCS_MSG_CLASS_TE_SPECIFIC = 0x03;
|
|
const PDU_DCS_MSG_CLASS_USER_1 = 0x04;
|
|
const PDU_DCS_MSG_CLASS_USER_2 = 0x05;
|
|
|
|
const GECKO_SMS_MESSAGE_CLASSES = {};
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_NORMAL] = "normal";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_0] = "class-0";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_ME_SPECIFIC] = "class-1";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_SIM_SPECIFIC] = "class-2";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_TE_SPECIFIC] = "class-3";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_1] = "user-1";
|
|
GECKO_SMS_MESSAGE_CLASSES[PDU_DCS_MSG_CLASS_USER_2] = "user-2";
|
|
|
|
const CB_MESSAGE_SIZE_GSM = 88;
|
|
const CB_MESSAGE_SIZE_ETWS = 56;
|
|
|
|
const CB_UMTS_MESSAGE_TYPE_CBS = 1;
|
|
const CB_UMTS_MESSAGE_PAGE_SIZE = 82;
|
|
|
|
const CB_GSM_MESSAGEID_ETWS_BEGIN = 0x1100;
|
|
const CB_GSM_MESSAGEID_ETWS_END = 0x1107;
|
|
|
|
const CB_GSM_GEOGRAPHICAL_SCOPE_NAMES = [
|
|
"cell-immediate",
|
|
"plmn",
|
|
"location-area",
|
|
"cell"
|
|
];
|
|
|
|
const CB_ETWS_WARNING_TYPE_NAMES = [
|
|
"earthquake",
|
|
"tsunami",
|
|
"earthquake-tsunami",
|
|
"test",
|
|
"other"
|
|
];
|
|
|
|
const CB_DCS_LANG_GROUP_1 = [
|
|
"de", "en", "it", "fr", "es", "nl", "sv", "da", "pt", "fi",
|
|
"no", "el", "tr", "hu", "pl", null
|
|
];
|
|
const CB_DCS_LANG_GROUP_2 = [
|
|
"cs", "he", "ar", "ru", "is", null, null, null, null, null,
|
|
null, null, null, null, null, null
|
|
];
|
|
|
|
const CB_MAX_CONTENT_PER_PAGE_7BIT = Math.floor((CB_MESSAGE_SIZE_GSM - 6) * 8 / 7);
|
|
const CB_MAX_CONTENT_PER_PAGE_UCS2 = Math.floor((CB_MESSAGE_SIZE_GSM - 6) / 2);
|
|
|
|
const DUMMY_BODY_7BITS = "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
|
+ "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
|
|
+ "@@@@@@@@@@@@@"; // 93 ascii chars.
|
|
const DUMMY_BODY_7BITS_IND = DUMMY_BODY_7BITS.substr(3);
|
|
const DUMMY_BODY_UCS2 = "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
|
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
|
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
|
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
|
+ "\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"
|
|
+ "\u0000"; // 41 unicode chars.
|
|
const DUMMY_BODY_UCS2_IND = DUMMY_BODY_UCS2.substr(1);
|
|
|
|
is(DUMMY_BODY_7BITS.length, CB_MAX_CONTENT_PER_PAGE_7BIT, "DUMMY_BODY_7BITS.length");
|
|
is(DUMMY_BODY_7BITS_IND.length, CB_MAX_CONTENT_PER_PAGE_7BIT - 3, "DUMMY_BODY_7BITS_IND.length");
|
|
is(DUMMY_BODY_UCS2.length, CB_MAX_CONTENT_PER_PAGE_UCS2, "DUMMY_BODY_UCS2.length");
|
|
is(DUMMY_BODY_UCS2_IND.length, CB_MAX_CONTENT_PER_PAGE_UCS2 - 1, "DUMMY_BODY_UCS2_IND.length");
|
|
|
|
/**
|
|
* Compose input number into specified number of semi-octets.
|
|
*
|
|
* @param: aNum
|
|
* The number to be converted.
|
|
* @param: aNumSemiOctets
|
|
* Number of semi-octects to be composed to.
|
|
*
|
|
* @return The composed Hex String.
|
|
*/
|
|
function buildHexStr(aNum, aNumSemiOctets) {
|
|
let str = aNum.toString(16);
|
|
ok(str.length <= aNumSemiOctets);
|
|
while (str.length < aNumSemiOctets) {
|
|
str = "0" + str;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
/**
|
|
* Helper function to decode the given DCS into encoding type, language,
|
|
* language indicator and message class.
|
|
*
|
|
* @param: aDcs
|
|
* The DCS to be decoded.
|
|
*
|
|
* @return [encoding, language, hasLanguageIndicator,
|
|
* GECKO_SMS_MESSAGE_CLASSES[messageClass]]
|
|
*/
|
|
function decodeGsmDataCodingScheme(aDcs) {
|
|
let language = null;
|
|
let hasLanguageIndicator = false;
|
|
let encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
|
|
let messageClass = PDU_DCS_MSG_CLASS_NORMAL;
|
|
|
|
switch (aDcs & PDU_DCS_CODING_GROUP_BITS) {
|
|
case 0x00: // 0000
|
|
language = CB_DCS_LANG_GROUP_1[aDcs & 0x0F];
|
|
break;
|
|
|
|
case 0x10: // 0001
|
|
switch (aDcs & 0x0F) {
|
|
case 0x00:
|
|
hasLanguageIndicator = true;
|
|
break;
|
|
case 0x01:
|
|
encoding = PDU_DCS_MSG_CODING_16BITS_ALPHABET;
|
|
hasLanguageIndicator = true;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case 0x20: // 0010
|
|
language = CB_DCS_LANG_GROUP_2[aDcs & 0x0F];
|
|
break;
|
|
|
|
case 0x40: // 01xx
|
|
case 0x50:
|
|
//case 0x60:
|
|
//case 0x70:
|
|
case 0x90: // 1001
|
|
encoding = (aDcs & 0x0C);
|
|
if (encoding == 0x0C) {
|
|
encoding = PDU_DCS_MSG_CODING_7BITS_ALPHABET;
|
|
}
|
|
messageClass = (aDcs & PDU_DCS_MSG_CLASS_BITS);
|
|
break;
|
|
|
|
case 0xF0:
|
|
encoding = (aDcs & 0x04) ? PDU_DCS_MSG_CODING_8BITS_ALPHABET
|
|
: PDU_DCS_MSG_CODING_7BITS_ALPHABET;
|
|
switch(aDcs & PDU_DCS_MSG_CLASS_BITS) {
|
|
case 0x01: messageClass = PDU_DCS_MSG_CLASS_USER_1; break;
|
|
case 0x02: messageClass = PDU_DCS_MSG_CLASS_USER_2; break;
|
|
case 0x03: messageClass = PDU_DCS_MSG_CLASS_TE_SPECIFIC; break;
|
|
}
|
|
break;
|
|
|
|
case 0x30: // 0011 (Reserved)
|
|
case 0x80: // 1000 (Reserved)
|
|
case 0xA0: // 1010..1100 (Reserved)
|
|
case 0xB0:
|
|
case 0xC0:
|
|
break;
|
|
default:
|
|
throw new Error("Unsupported CBS data coding scheme: " + aDcs);
|
|
}
|
|
|
|
return [encoding, language, hasLanguageIndicator,
|
|
GECKO_SMS_MESSAGE_CLASSES[messageClass]];
|
|
}
|
|
|
|
/**
|
|
* Push required permissions and test if |navigator.mozCellBroadcast| exists.
|
|
* Resolve if it does, reject otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* cbManager -- an reference to navigator.mozCellBroadcast.
|
|
*
|
|
* Reject params: (none)
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
var cbManager;
|
|
function ensureCellBroadcast() {
|
|
let deferred = Promise.defer();
|
|
|
|
let permissions = [{
|
|
"type": "cellbroadcast",
|
|
"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 workingFrame = document.createElement("iframe");
|
|
workingFrame.addEventListener("load", function load() {
|
|
workingFrame.removeEventListener("load", load);
|
|
|
|
cbManager = workingFrame.contentWindow.navigator.mozCellBroadcast;
|
|
|
|
if (cbManager) {
|
|
log("navigator.mozCellBroadcast is instance of " + cbManager.constructor);
|
|
} else {
|
|
log("navigator.mozCellBroadcast is undefined.");
|
|
}
|
|
|
|
if (cbManager instanceof window.MozCellBroadcast) {
|
|
deferred.resolve(cbManager);
|
|
} else {
|
|
deferred.reject();
|
|
}
|
|
});
|
|
|
|
document.body.appendChild(workingFrame);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Send emulator command with safe guard.
|
|
*
|
|
* We should only call |finish()| after all emulator command transactions
|
|
* end, so here comes with the pending counter. Resolve when the emulator
|
|
* gives positive response, and reject otherwise.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
var pendingEmulatorCmdCount = 0;
|
|
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[aResult.length - 1] === "OK") {
|
|
deferred.resolve(aResult);
|
|
} else {
|
|
deferred.reject(aResult);
|
|
}
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Send raw CBS PDU to emulator.
|
|
*
|
|
* @param: aPdu
|
|
* A hex string representing the whole CBS PDU.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function sendRawCbsToEmulator(aPdu) {
|
|
let command = "cbs pdu " + aPdu;
|
|
return runEmulatorCmdSafe(command);
|
|
}
|
|
|
|
/**
|
|
* Wait for one named Cellbroadcast 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();
|
|
|
|
cbManager.addEventListener(aEventName, function onevent(aEvent) {
|
|
cbManager.removeEventListener(aEventName, onevent);
|
|
|
|
ok(true, "Cellbroadcast event '" + aEventName + "' got.");
|
|
deferred.resolve(aEvent);
|
|
});
|
|
|
|
return deferred.promise;
|
|
}
|
|
|
|
/**
|
|
* Send multiple raw CB PDU to emulator and wait
|
|
*
|
|
* @param: aPdus
|
|
* A array of hex strings. Each represents a CB PDU.
|
|
* These PDUs are expected to be concatenated into single CB Message.
|
|
*
|
|
* 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 sendMultipleRawCbsToEmulatorAndWait(aPdus) {
|
|
let promises = [];
|
|
|
|
promises.push(waitForManagerEvent("received"));
|
|
for (let pdu of aPdus) {
|
|
promises.push(sendRawCbsToEmulator(pdu));
|
|
}
|
|
|
|
return Promise.all(promises).then(aResults => aResults[0].message);
|
|
}
|
|
|
|
/**
|
|
* Wait for pending emulator transactions and call |finish()|.
|
|
*/
|
|
function cleanUp() {
|
|
// Use ok here so that we have at least one test run.
|
|
ok(true, ":: CLEANING UP ::");
|
|
|
|
waitFor(finish, function() {
|
|
return pendingEmulatorCmdCount === 0;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Switch modem for receving upcoming emulator commands.
|
|
*
|
|
* @param: aServiceId
|
|
* The id of the modem to be switched to.
|
|
*
|
|
* Fulfill params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* Reject params:
|
|
* result -- an array of emulator response lines.
|
|
*
|
|
* @return A deferred promise.
|
|
*/
|
|
function selectModem(aServiceId) {
|
|
let command = "mux modem " + aServiceId;
|
|
return runEmulatorCmdSafe(command);
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Common test routine helper for cell broadcast tests.
|
|
*
|
|
* This function ensures global |cbManager| variable is available during the
|
|
* process and performs clean-ups as well.
|
|
*
|
|
* @param aTestCaseMain
|
|
* A function that takes no parameter.
|
|
*/
|
|
function startTestCommon(aTestCaseMain) {
|
|
Promise.resolve()
|
|
.then(ensureCellBroadcast)
|
|
.then(aTestCaseMain)
|
|
.then(cleanUp, function() {
|
|
ok(false, 'promise rejects during test.');
|
|
cleanUp();
|
|
});
|
|
}
|