Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 let Promise = SpecialPowers.Cu.import("resource://gre/modules/Promise.jsm").Promise;
5 let telephony;
6 let conference;
8 /**
9 * Emulator helper.
10 */
11 let emulator = (function() {
12 let pendingCmdCount = 0;
13 let originalRunEmulatorCmd = runEmulatorCmd;
15 // Overwritten it so people could not call this function directly.
16 runEmulatorCmd = function() {
17 throw "Use emulator.run(cmd, callback) instead of runEmulatorCmd";
18 };
20 function run(cmd, callback) {
21 pendingCmdCount++;
22 originalRunEmulatorCmd(cmd, function(result) {
23 pendingCmdCount--;
24 if (callback && typeof callback === "function") {
25 callback(result);
26 }
27 });
28 }
30 /**
31 * @return Promise
32 */
33 function waitFinish() {
34 let deferred = Promise.defer();
36 waitFor(function() {
37 deferred.resolve();
38 }, function() {
39 return pendingCmdCount === 0;
40 });
42 return deferred.promise;
43 }
45 return {
46 run: run,
47 waitFinish: waitFinish
48 };
49 }());
51 // Delay 1s before each telephony.dial()
52 // The workaround here should be removed after bug 1005816.
54 let originalDial;
56 function delayTelephonyDial() {
57 originalDial = telephony.dial;
58 telephony.dial = function(number, serviceId) {
59 let deferred = Promise.defer();
61 let startTime = Date.now();
62 waitFor(function() {
63 originalDial.call(telephony, number, serviceId).then(call => {
64 deferred.resolve(call);
65 }, cause => {
66 deferred.reject(cause);
67 });
68 }, function() {
69 duration = Date.now() - startTime;
70 return (duration >= 1000);
71 });
73 return deferred.promise;
74 };
75 }
77 function restoreTelephonyDial() {
78 telephony.dial = originalDial;
79 }
81 /**
82 * Telephony related helper functions.
83 */
84 (function() {
85 /**
86 * @return Promise
87 */
88 function clearCalls() {
89 let deferred = Promise.defer();
91 log("Clear existing calls.");
92 emulator.run("gsm clear", function(result) {
93 if (result[0] == "OK") {
94 waitFor(function() {
95 deferred.resolve();
96 }, function() {
97 return telephony.calls.length === 0;
98 });
99 } else {
100 log("Failed to clear existing calls.");
101 deferred.reject();
102 }
103 });
105 return deferred.promise;
106 }
108 /**
109 * Provide a string with format of the emulator call list result.
110 *
111 * @param prefix
112 * Possible values are "outbound" and "inbound".
113 * @param number
114 * Call number.
115 * @return A string with format of the emulator call list result.
116 */
117 function callStrPool(prefix, number) {
118 let padding = " : ";
119 let numberInfo = prefix + number + padding.substr(number.length);
121 let info = {};
122 let states = ['ringing', 'incoming', 'active', 'held'];
123 for (let state of states) {
124 info[state] = numberInfo + state;
125 }
127 return info;
128 }
130 /**
131 * Provide a corresponding string of an outgoing call. The string is with
132 * format of the emulator call list result.
133 *
134 * @param number
135 * Number of an outgoing call.
136 * @return A string with format of the emulator call list result.
137 */
138 function outCallStrPool(number) {
139 return callStrPool("outbound to ", number);
140 }
142 /**
143 * Provide a corresponding string of an incoming call. The string is with
144 * format of the emulator call list result.
145 *
146 * @param number
147 * Number of an incoming call.
148 * @return A string with format of the emulator call list result.
149 */
150 function inCallStrPool(number) {
151 return callStrPool("inbound from ", number);
152 }
154 /**
155 * Check utility functions.
156 */
158 function checkInitialState() {
159 log("Verify initial state.");
160 ok(telephony.calls, 'telephony.call');
161 checkTelephonyActiveAndCalls(null, []);
162 ok(conference.calls, 'conference.calls');
163 checkConferenceStateAndCalls('', []);
164 }
166 /**
167 * Convenient helper to compare a TelephonyCall and a received call event.
168 */
169 function checkEventCallState(event, call, state) {
170 is(call, event.call, "event.call");
171 is(call.state, state, "call state");
172 }
174 /**
175 * Convenient helper to check mozTelephony.active and mozTelephony.calls.
176 */
177 function checkTelephonyActiveAndCalls(active, calls) {
178 is(telephony.active, active, "telephony.active");
179 is(telephony.calls.length, calls.length, "telephony.calls");
180 for (let i = 0; i < calls.length; ++i) {
181 is(telephony.calls[i], calls[i]);
182 }
183 }
185 /**
186 * Convenient helper to check mozTelephony.conferenceGroup.state and
187 * .conferenceGroup.calls.
188 */
189 function checkConferenceStateAndCalls(state, calls) {
190 is(conference.state, state, "conference.state");
191 is(conference.calls.length, calls.length, "conference.calls");
192 for (let i = 0; i < calls.length; i++) {
193 is(conference.calls[i], calls[i]);
194 }
195 }
197 /**
198 * Convenient helper to handle *.oncallschanged event.
199 *
200 * @param container
201 * Representation of "mozTelephony" or "mozTelephony.conferenceGroup."
202 * @param containerName
203 * Name of container. Could be an arbitrary string, used for debug
204 * messages only.
205 * @param expectedCalls
206 * An array of calls.
207 * @param callback
208 * A callback function.
209 */
210 function check_oncallschanged(container, containerName, expectedCalls,
211 callback) {
212 container.oncallschanged = function(event) {
213 log("Received 'callschanged' event for the " + containerName);
214 if (event.call) {
215 let index = expectedCalls.indexOf(event.call);
216 ok(index != -1);
217 expectedCalls.splice(index, 1);
219 if (expectedCalls.length === 0) {
220 container.oncallschanged = null;
221 callback();
222 }
223 }
224 };
225 }
227 /**
228 * Convenient helper to handle *.ongroupchange event.
229 *
230 * @param call
231 * A TelephonyCall object.
232 * @param callName
233 * Name of a call. Could be an arbitrary string, used for debug messages
234 * only.
235 * @param group
236 * Representation of mozTelephony.conferenceGroup.
237 * @param callback
238 * A callback function.
239 */
240 function check_ongroupchange(call, callName, group, callback) {
241 call.ongroupchange = function(event) {
242 log("Received 'groupchange' event for the " + callName);
243 call.ongroupchange = null;
245 is(call.group, group);
246 callback();
247 };
248 }
250 /**
251 * Convenient helper to handle *.onstatechange event.
252 *
253 * @param container
254 * Representation of a TelephonyCall or mozTelephony.conferenceGroup.
255 * @param containerName
256 * Name of container. Could be an arbitrary string, used for debug messages
257 * only.
258 * @param state
259 * A string.
260 * @param callback
261 * A callback function.
262 */
263 function check_onstatechange(container, containerName, state, callback) {
264 container.onstatechange = function(event) {
265 log("Received 'statechange' event for the " + containerName);
266 container.onstatechange = null;
268 is(container.state, state);
269 callback();
270 };
271 }
273 /**
274 * Convenient helper to check the sequence of call state and event handlers.
275 *
276 * @param state
277 * A string of the expected call state.
278 * @param previousEvent
279 * A string of the event that should come before the expected state.
280 */
281 function StateEventChecker(state, previousEvent) {
282 let event = 'on' + state;
284 return function(call, callName, callback) {
285 call[event] = function() {
286 log("Received '" + state + "' event for the " + callName);
287 call[event] = null;
289 if (previousEvent) {
290 // We always clear the event handler when the event is received.
291 // Therefore, if the corresponding handler is not existed, the expected
292 // previous event has been already received.
293 ok(!call[previousEvent]);
294 }
295 is(call.state, state);
296 callback();
297 };
298 };
299 }
301 /**
302 * Convenient helper to check the call list existing in the emulator.
303 *
304 * @param expectedCallList
305 * An array of call info with the format of "callStrPool()[state]".
306 * @return A deferred promise.
307 */
308 function checkEmulatorCallList(expectedCallList) {
309 let deferred = Promise.defer();
311 emulator.run("gsm list", function(result) {
312 log("Call list is now: " + result);
313 for (let i = 0; i < expectedCallList.length; ++i) {
314 is(result[i], expectedCallList[i], "emulator calllist");
315 }
316 is(result[expectedCallList.length], "OK", "emulator calllist");
317 deferred.resolve();
318 });
320 return deferred.promise;
321 }
323 /**
324 * Super convenient helper to check calls and state of mozTelephony and
325 * mozTelephony.conferenceGroup.
326 *
327 * @param active
328 * A TelephonyCall object. Should be the expected active call.
329 * @param calls
330 * An array of TelephonyCall objects. Should be the expected list of
331 * mozTelephony.calls.
332 * @param conferenceState
333 * A string. Should be the expected conference state.
334 * @param conferenceCalls
335 * An array of TelephonyCall objects. Should be the expected list of
336 * mozTelephony.conferenceGroup.calls.
337 */
338 function checkState(active, calls, conferenceState, conferenceCalls) {
339 checkTelephonyActiveAndCalls(active, calls);
340 checkConferenceStateAndCalls(conferenceState, conferenceCalls);
341 }
343 /**
344 * Super convenient helper to check calls and state of mozTelephony and
345 * mozTelephony.conferenceGroup as well as the calls existing in the emulator.
346 *
347 * @param active
348 * A TelephonyCall object. Should be the expected active call.
349 * @param calls
350 * An array of TelephonyCall objects. Should be the expected list of
351 * mozTelephony.calls.
352 * @param conferenceState
353 * A string. Should be the expected conference state.
354 * @param conferenceCalls
355 * An array of TelephonyCall objects. Should be the expected list of
356 * mozTelephony.conferenceGroup.calls.
357 * @param callList
358 * An array of call info with the format of "callStrPool()[state]".
359 * @return A deferred promise.
360 */
361 function checkAll(active, calls, conferenceState, conferenceCalls, callList) {
362 checkState(active, calls, conferenceState, conferenceCalls);
363 return checkEmulatorCallList(callList);
364 }
366 /**
367 * Request utility functions.
368 */
370 /**
371 * Make sure there's no pending event before we jump to the next action.
372 *
373 * @param received
374 * A string of the received event.
375 * @param pending
376 * An array of the pending events.
377 * @param nextAction
378 * A callback function that is called when there's no pending event.
379 */
380 function receivedPending(received, pending, nextAction) {
381 let index = pending.indexOf(received);
382 if (index != -1) {
383 pending.splice(index, 1);
384 }
385 if (pending.length === 0) {
386 nextAction();
387 }
388 }
390 /**
391 * Make an outgoing call.
392 *
393 * @param number
394 * A string.
395 * @param serviceId [optional]
396 * Identification of a service. 0 is set as default.
397 * @return A deferred promise.
398 */
399 function dial(number, serviceId) {
400 serviceId = typeof serviceId !== "undefined" ? serviceId : 0;
401 log("Make an outgoing call: " + number + ", serviceId: " + serviceId);
403 let deferred = Promise.defer();
405 telephony.dial(number, serviceId).then(call => {
406 ok(call);
407 is(call.number, number);
408 is(call.state, "dialing");
409 is(call.serviceId, serviceId);
411 call.onalerting = function onalerting(event) {
412 call.onalerting = null;
413 log("Received 'onalerting' call event.");
414 checkEventCallState(event, call, "alerting");
415 deferred.resolve(call);
416 };
417 }, cause => {
418 deferred.reject(cause);
419 });
421 return deferred.promise;
422 }
424 /**
425 * Answer an incoming call.
426 *
427 * @param call
428 * An incoming TelephonyCall object.
429 * @param conferenceStateChangeCallback [optional]
430 * A callback function which is called if answering an incoming call
431 * triggers conference state change.
432 * @return A deferred promise.
433 */
434 function answer(call, conferenceStateChangeCallback) {
435 log("Answering the incoming call.");
437 let deferred = Promise.defer();
438 let done = function() {
439 deferred.resolve(call);
440 };
442 let pending = ["call.onconnected"];
443 let receive = function(name) {
444 receivedPending(name, pending, done);
445 };
447 // When there's already a connected conference call, answering a new incoming
448 // call triggers conference state change. We should wait for
449 // |conference.onstatechange| before checking the state of the conference call.
450 if (conference.state === "connected") {
451 pending.push("conference.onstatechange");
452 check_onstatechange(conference, "conference", "held", function() {
453 if (typeof conferenceStateChangeCallback === "function") {
454 conferenceStateChangeCallback();
455 }
456 receive("conference.onstatechange");
457 });
458 }
460 call.onconnecting = function onconnectingIn(event) {
461 log("Received 'connecting' call event for incoming call.");
462 call.onconnecting = null;
463 checkEventCallState(event, call, "connecting");
464 };
466 call.onconnected = function onconnectedIn(event) {
467 log("Received 'connected' call event for incoming call.");
468 call.onconnected = null;
469 checkEventCallState(event, call, "connected");
470 ok(!call.onconnecting);
471 receive("call.onconnected");
472 };
473 call.answer();
475 return deferred.promise;
476 }
478 /**
479 * Hold a call.
480 *
481 * @param call
482 * A TelephonyCall object.
483 * @return A deferred promise.
484 */
485 function hold(call) {
486 log("Putting the call on hold.");
488 let deferred = Promise.defer();
490 let gotHolding = false;
491 call.onholding = function onholding(event) {
492 log("Received 'holding' call event");
493 call.onholding = null;
494 checkEventCallState(event, call, "holding");
495 gotHolding = true;
496 };
498 call.onheld = function onheld(event) {
499 log("Received 'held' call event");
500 call.onheld = null;
501 checkEventCallState(event, call, "held");
502 ok(gotHolding);
503 deferred.resolve(call);
504 };
505 call.hold();
507 return deferred.promise;
508 }
510 /**
511 * Simulate an incoming call.
512 *
513 * @param number
514 * A string.
515 * @return A deferred promise.
516 */
517 function remoteDial(number) {
518 log("Simulating an incoming call.");
520 let deferred = Promise.defer();
522 telephony.onincoming = function onincoming(event) {
523 log("Received 'incoming' call event.");
524 telephony.onincoming = null;
526 let call = event.call;
528 ok(call);
529 is(call.number, number);
530 is(call.state, "incoming");
532 deferred.resolve(call);
533 };
534 emulator.run("gsm call " + number);
536 return deferred.promise;
537 }
539 /**
540 * Remote party answers the call.
541 *
542 * @param call
543 * A TelephonyCall object.
544 * @return A deferred promise.
545 */
546 function remoteAnswer(call) {
547 log("Remote answering the call.");
549 let deferred = Promise.defer();
551 call.onconnected = function onconnected(event) {
552 log("Received 'connected' call event.");
553 call.onconnected = null;
554 checkEventCallState(event, call, "connected");
555 deferred.resolve(call);
556 };
557 emulator.run("gsm accept " + call.number);
559 return deferred.promise;
560 }
562 /**
563 * Remote party hangs up the call.
564 *
565 * @param call
566 * A TelephonyCall object.
567 * @return A deferred promise.
568 */
569 function remoteHangUp(call) {
570 log("Remote hanging up the call.");
572 let deferred = Promise.defer();
574 call.ondisconnected = function ondisconnected(event) {
575 log("Received 'disconnected' call event.");
576 call.ondisconnected = null;
577 checkEventCallState(event, call, "disconnected");
578 deferred.resolve(call);
579 };
580 emulator.run("gsm cancel " + call.number);
582 return deferred.promise;
583 }
585 /**
586 * Remote party hangs up all the calls.
587 *
588 * @param calls
589 * An array of TelephonyCall objects.
590 * @return A deferred promise.
591 */
592 function remoteHangUpCalls(calls) {
593 let promise = Promise.resolve();
595 for (let call of calls) {
596 promise = promise.then(remoteHangUp.bind(null, call));
597 }
599 return promise;
600 }
602 /**
603 * Add calls to conference.
604 *
605 * @param callsToAdd
606 * An array of TelephonyCall objects to be added into conference. The
607 * length of the array should be 1 or 2.
608 * @param connectedCallback [optional]
609 * A callback function which is called when conference state becomes
610 * connected.
611 * @return A deferred promise.
612 */
613 function addCallsToConference(callsToAdd, connectedCallback) {
614 log("Add " + callsToAdd.length + " calls into conference.");
616 let deferred = Promise.defer();
617 let done = function() {
618 deferred.resolve();
619 };
621 let pending = ["conference.oncallschanged", "conference.onconnected"];
622 let receive = function(name) {
623 receivedPending(name, pending, done);
624 };
626 let check_onconnected = StateEventChecker('connected', 'onresuming');
628 for (let call of callsToAdd) {
629 let callName = "callToAdd (" + call.number + ')';
631 let ongroupchange = callName + ".ongroupchange";
632 pending.push(ongroupchange);
633 check_ongroupchange(call, callName, conference,
634 receive.bind(null, ongroupchange));
636 let onstatechange = callName + ".onstatechange";
637 pending.push(onstatechange);
638 check_onstatechange(call, callName, 'connected',
639 receive.bind(null, onstatechange));
640 }
642 check_oncallschanged(conference, 'conference', callsToAdd,
643 receive.bind(null, "conference.oncallschanged"));
645 check_onconnected(conference, "conference", function() {
646 ok(!conference.oncallschanged);
647 if (typeof connectedCallback === 'function') {
648 connectedCallback();
649 }
650 receive("conference.onconnected");
651 });
653 // Cannot use apply() through webidl, so just separate the cases to handle.
654 if (callsToAdd.length == 2) {
655 conference.add(callsToAdd[0], callsToAdd[1]);
656 } else {
657 conference.add(callsToAdd[0]);
658 }
660 return deferred.promise;
661 }
663 /**
664 * Hold the conference.
665 *
666 * @param calls
667 * An array of TelephonyCall objects existing in conference.
668 * @param heldCallback [optional]
669 * A callback function which is called when conference state becomes
670 * held.
671 * @return A deferred promise.
672 */
673 function holdConference(calls, heldCallback) {
674 log("Holding the conference call.");
676 let deferred = Promise.defer();
677 let done = function() {
678 deferred.resolve();
679 };
681 let pending = ["conference.onholding", "conference.onheld"];
682 let receive = function(name) {
683 receivedPending(name, pending, done);
684 };
686 let check_onholding = StateEventChecker('holding', null);
687 let check_onheld = StateEventChecker('held', 'onholding');
689 for (let call of calls) {
690 let callName = "call (" + call.number + ')';
692 let onholding = callName + ".onholding";
693 pending.push(onholding);
694 check_onholding(call, callName, receive.bind(null, onholding));
696 let onheld = callName + ".onheld";
697 pending.push(onheld);
698 check_onheld(call, callName, receive.bind(null, onheld));
699 }
701 check_onholding(conference, "conference",
702 receive.bind(null, "conference.onholding"));
704 check_onheld(conference, "conference", function() {
705 if (typeof heldCallback === 'function') {
706 heldCallback();
707 }
708 receive("conference.onheld");
709 });
711 conference.hold();
713 return deferred.promise;
714 }
716 /**
717 * Resume the conference.
718 *
719 * @param calls
720 * An array of TelephonyCall objects existing in conference.
721 * @param connectedCallback [optional]
722 * A callback function which is called when conference state becomes
723 * connected.
724 * @return A deferred promise.
725 */
726 function resumeConference(calls, connectedCallback) {
727 log("Resuming the held conference call.");
729 let deferred = Promise.defer();
730 let done = function() {
731 deferred.resolve();
732 };
734 let pending = ["conference.onresuming", "conference.onconnected"];
735 let receive = function(name) {
736 receivedPending(name, pending, done);
737 };
739 let check_onresuming = StateEventChecker('resuming', null);
740 let check_onconnected = StateEventChecker('connected', 'onresuming');
742 for (let call of calls) {
743 let callName = "call (" + call.number + ')';
745 let onresuming = callName + ".onresuming";
746 pending.push(onresuming);
747 check_onresuming(call, callName, receive.bind(null, onresuming));
749 let onconnected = callName + ".onconnected";
750 pending.push(onconnected);
751 check_onconnected(call, callName, receive.bind(null, onconnected));
752 }
754 check_onresuming(conference, "conference",
755 receive.bind(null, "conference.onresuming"));
757 check_onconnected(conference, "conference", function() {
758 if (typeof connectedCallback === 'function') {
759 connectedCallback();
760 }
761 receive("conference.onconnected");
762 });
764 conference.resume();
766 return deferred.promise;
767 }
769 /**
770 * Remove a call out of conference.
771 *
772 * @param callToRemove
773 * A TelephonyCall object existing in conference.
774 * @param autoRemovedCalls
775 * An array of TelephonyCall objects which is going to be automatically
776 * removed. The length of the array should be 0 or 1.
777 * @param remainedCalls
778 * An array of TelephonyCall objects which remain in conference.
779 * @param stateChangeCallback [optional]
780 * A callback function which is called when conference state changes.
781 * @return A deferred promise.
782 */
783 function removeCallInConference(callToRemove, autoRemovedCalls, remainedCalls,
784 statechangeCallback) {
785 log("Removing a participant from the conference call.");
787 is(conference.state, 'connected');
789 let deferred = Promise.defer();
790 let done = function() {
791 deferred.resolve();
792 };
794 let pending = ["callToRemove.ongroupchange", "telephony.oncallschanged",
795 "conference.oncallschanged", "conference.onstatechange"];
796 let receive = function(name) {
797 receivedPending(name, pending, done);
798 };
800 // Remained call in conference will be held.
801 for (let call of remainedCalls) {
802 let callName = "remainedCall (" + call.number + ')';
804 let onstatechange = callName + ".onstatechange";
805 pending.push(onstatechange);
806 check_onstatechange(call, callName, 'held',
807 receive.bind(null, onstatechange));
808 }
810 // When a call is removed from conference with 2 calls, another one will be
811 // automatically removed from group and be put on hold.
812 for (let call of autoRemovedCalls) {
813 let callName = "autoRemovedCall (" + call.number + ')';
815 let ongroupchange = callName + ".ongroupchange";
816 pending.push(ongroupchange);
817 check_ongroupchange(call, callName, null,
818 receive.bind(null, ongroupchange));
820 let onstatechange = callName + ".onstatechange";
821 pending.push(onstatechange);
822 check_onstatechange(call, callName, 'held',
823 receive.bind(null, onstatechange));
824 }
826 check_ongroupchange(callToRemove, "callToRemove", null, function() {
827 is(callToRemove.state, 'connected');
828 receive("callToRemove.ongroupchange");
829 });
831 check_oncallschanged(telephony, 'telephony',
832 autoRemovedCalls.concat(callToRemove),
833 receive.bind(null, "telephony.oncallschanged"));
835 check_oncallschanged(conference, 'conference',
836 autoRemovedCalls.concat(callToRemove), function() {
837 is(conference.calls.length, remainedCalls.length);
838 receive("conference.oncallschanged");
839 });
841 check_onstatechange(conference, 'conference',
842 (remainedCalls.length ? 'held' : ''), function() {
843 ok(!conference.oncallschanged);
844 if (typeof statechangeCallback === 'function') {
845 statechangeCallback();
846 }
847 receive("conference.onstatechange");
848 });
850 conference.remove(callToRemove);
852 return deferred.promise;
853 }
855 /**
856 * Hangup a call in conference.
857 *
858 * @param callToHangUp
859 * A TelephonyCall object existing in conference.
860 * @param autoRemovedCalls
861 * An array of TelephonyCall objects which is going to be automatically
862 * removed. The length of the array should be 0 or 1.
863 * @param remainedCalls
864 * An array of TelephonyCall objects which remain in conference.
865 * @param stateChangeCallback [optional]
866 * A callback function which is called when conference state changes.
867 * @return A deferred promise.
868 */
869 function hangUpCallInConference(callToHangUp, autoRemovedCalls, remainedCalls,
870 statechangeCallback) {
871 log("Release one call in conference.");
873 let deferred = Promise.defer();
874 let done = function() {
875 deferred.resolve();
876 };
878 let pending = ["conference.oncallschanged", "remoteHangUp"];
879 let receive = function(name) {
880 receivedPending(name, pending, done);
881 };
883 // When a call is hang up from conference with 2 calls, another one will be
884 // automatically removed from group.
885 for (let call of autoRemovedCalls) {
886 let callName = "autoRemovedCall (" + call.number + ')';
888 let ongroupchange = callName + ".ongroupchange";
889 pending.push(ongroupchange);
890 check_ongroupchange(call, callName, null,
891 receive.bind(null, ongroupchange));
892 }
894 if (autoRemovedCalls.length) {
895 pending.push("telephony.oncallschanged");
896 check_oncallschanged(telephony, 'telephony',
897 autoRemovedCalls,
898 receive.bind(null, "telephony.oncallschanged"));
899 }
901 check_oncallschanged(conference, 'conference',
902 autoRemovedCalls.concat(callToHangUp), function() {
903 is(conference.calls.length, remainedCalls.length);
904 receive("conference.oncallschanged");
905 });
907 if (remainedCalls.length === 0) {
908 pending.push("conference.onstatechange");
909 check_onstatechange(conference, 'conference', '', function() {
910 ok(!conference.oncallschanged);
911 if (typeof statechangeCallback === 'function') {
912 statechangeCallback();
913 }
914 receive("conference.onstatechange");
915 });
916 }
918 remoteHangUp(callToHangUp)
919 .then(receive.bind(null, "remoteHangUp"));
921 return deferred.promise;
922 }
924 /**
925 * Setup a conference with an outgoing call and an incoming call.
926 *
927 * @param outNumber
928 * Number of an outgoing call.
929 * @param inNumber
930 * Number of an incoming call.
931 * @return Promise<[outCall, inCall]>
932 */
933 function setupConferenceTwoCalls(outNumber, inNumber) {
934 log('Create conference with two calls.');
936 let outCall;
937 let inCall;
938 let outInfo = outCallStrPool(outNumber);
939 let inInfo = inCallStrPool(inNumber);
941 return Promise.resolve()
942 .then(checkInitialState)
943 .then(() => dial(outNumber))
944 .then(call => { outCall = call; })
945 .then(() => checkAll(outCall, [outCall], '', [], [outInfo.ringing]))
946 .then(() => remoteAnswer(outCall))
947 .then(() => checkAll(outCall, [outCall], '', [], [outInfo.active]))
948 .then(() => remoteDial(inNumber))
949 .then(call => { inCall = call; })
950 .then(() => checkAll(outCall, [outCall, inCall], '', [],
951 [outInfo.active, inInfo.incoming]))
952 .then(() => answer(inCall))
953 .then(() => checkAll(inCall, [outCall, inCall], '', [],
954 [outInfo.held, inInfo.active]))
955 .then(() => addCallsToConference([outCall, inCall], function() {
956 checkState(conference, [], 'connected', [outCall, inCall]);
957 }))
958 .then(() => checkAll(conference, [], 'connected', [outCall, inCall],
959 [outInfo.active, inInfo.active]))
960 .then(() => {
961 return [outCall, inCall];
962 });
963 }
965 /**
966 * Setup a conference with an outgoing call and two incoming calls.
967 *
968 * @param outNumber
969 * Number of an outgoing call.
970 * @param inNumber
971 * Number of an incoming call.
972 * @param inNumber2
973 * Number of an incoming call.
974 * @return Promise<[outCall, inCall, inCall2]>
975 */
976 function setupConferenceThreeCalls(outNumber, inNumber, inNumber2) {
977 log('Create conference with three calls.');
979 let outCall;
980 let inCall;
981 let inCall2;
982 let outInfo = outCallStrPool(outNumber);
983 let inInfo = inCallStrPool(inNumber);
984 let inInfo2 = inCallStrPool(inNumber2);
986 return Promise.resolve()
987 .then(() => setupConferenceTwoCalls(outNumber, inNumber))
988 .then(calls => {
989 outCall = calls[0];
990 inCall = calls[1];
991 })
992 .then(() => remoteDial(inNumber2))
993 .then(call => { inCall2 = call; })
994 .then(() => checkAll(conference, [inCall2], 'connected', [outCall, inCall],
995 [outInfo.active, inInfo.active, inInfo2.incoming]))
996 .then(() => answer(inCall2, function() {
997 checkState(inCall2, [inCall2], 'held', [outCall, inCall]);
998 }))
999 .then(() => checkAll(inCall2, [inCall2], 'held', [outCall, inCall],
1000 [outInfo.held, inInfo.held, inInfo2.active]))
1001 .then(() => addCallsToConference([inCall2], function() {
1002 checkState(conference, [], 'connected', [outCall, inCall, inCall2]);
1003 }))
1004 .then(() => checkAll(conference, [],
1005 'connected', [outCall, inCall, inCall2],
1006 [outInfo.active, inInfo.active, inInfo2.active]))
1007 .then(() => {
1008 return [outCall, inCall, inCall2];
1009 });
1010 }
1012 /**
1013 * Setup a conference with an outgoing call and four incoming calls.
1014 *
1015 * @param outNumber
1016 * Number of an outgoing call.
1017 * @param inNumber
1018 * Number of an incoming call.
1019 * @param inNumber2
1020 * Number of an incoming call.
1021 * @param inNumber3
1022 * Number of an incoming call.
1023 * @param inNumber4
1024 * Number of an incoming call.
1025 * @return Promise<[outCall, inCall, inCall2, inCall3, inCall4]>
1026 */
1027 function setupConferenceFiveCalls(outNumber, inNumber, inNumber2, inNumber3,
1028 inNumber4) {
1029 log('Create conference with five calls.');
1031 let outCall;
1032 let inCall;
1033 let inCall2;
1034 let inCall3;
1035 let inCall4;
1036 let outInfo = outCallStrPool(outNumber);
1037 let inInfo = inCallStrPool(inNumber);
1038 let inInfo2 = inCallStrPool(inNumber2);
1039 let inInfo3 = inCallStrPool(inNumber3);
1040 let inInfo4 = inCallStrPool(inNumber4);
1042 return Promise.resolve()
1043 .then(() => setupConferenceThreeCalls(outNumber, inNumber, inNumber2))
1044 .then(calls => {
1045 [outCall, inCall, inCall2] = calls;
1046 })
1047 .then(() => remoteDial(inNumber3))
1048 .then(call => {inCall3 = call;})
1049 .then(() => checkAll(conference, [inCall3], 'connected',
1050 [outCall, inCall, inCall2],
1051 [outInfo.active, inInfo.active, inInfo2.active,
1052 inInfo3.incoming]))
1053 .then(() => answer(inCall3, function() {
1054 checkState(inCall3, [inCall3], 'held', [outCall, inCall, inCall2]);
1055 }))
1056 .then(() => checkAll(inCall3, [inCall3], 'held',
1057 [outCall, inCall, inCall2],
1058 [outInfo.held, inInfo.held, inInfo2.held,
1059 inInfo3.active]))
1060 .then(() => addCallsToConference([inCall3], function() {
1061 checkState(conference, [], 'connected', [outCall, inCall, inCall2, inCall3]);
1062 }))
1063 .then(() => checkAll(conference, [], 'connected',
1064 [outCall, inCall, inCall2, inCall3],
1065 [outInfo.active, inInfo.active, inInfo2.active,
1066 inInfo3.active]))
1067 .then(() => remoteDial(inNumber4))
1068 .then(call => {inCall4 = call;})
1069 .then(() => checkAll(conference, [inCall4], 'connected',
1070 [outCall, inCall, inCall2, inCall3],
1071 [outInfo.active, inInfo.active, inInfo2.active,
1072 inInfo3.active, inInfo4.incoming]))
1073 .then(() => answer(inCall4, function() {
1074 checkState(inCall4, [inCall4], 'held', [outCall, inCall, inCall2, inCall3]);
1075 }))
1076 .then(() => checkAll(inCall4, [inCall4], 'held',
1077 [outCall, inCall, inCall2, inCall3],
1078 [outInfo.held, inInfo.held, inInfo2.held,
1079 inInfo3.held, inInfo4.active]))
1080 .then(() => addCallsToConference([inCall4], function() {
1081 checkState(conference, [], 'connected', [outCall, inCall, inCall2,
1082 inCall3, inCall4]);
1083 }))
1084 .then(() => checkAll(conference, [], 'connected',
1085 [outCall, inCall, inCall2, inCall3, inCall4],
1086 [outInfo.active, inInfo.active, inInfo2.active,
1087 inInfo3.active, inInfo4.active]))
1088 .then(() => {
1089 return [outCall, inCall, inCall2, inCall3, inCall4];
1090 });
1091 }
1093 /**
1094 * Public members.
1095 */
1097 this.gCheckInitialState = checkInitialState;
1098 this.gClearCalls = clearCalls;
1099 this.gOutCallStrPool = outCallStrPool;
1100 this.gInCallStrPool = inCallStrPool;
1101 this.gCheckState = checkState;
1102 this.gCheckAll = checkAll;
1103 this.gDial = dial;
1104 this.gAnswer = answer;
1105 this.gHold = hold;
1106 this.gRemoteDial = remoteDial;
1107 this.gRemoteAnswer = remoteAnswer;
1108 this.gRemoteHangUp = remoteHangUp;
1109 this.gRemoteHangUpCalls = remoteHangUpCalls;
1110 this.gAddCallsToConference = addCallsToConference;
1111 this.gHoldConference = holdConference;
1112 this.gResumeConference = resumeConference;
1113 this.gRemoveCallInConference = removeCallInConference;
1114 this.gHangUpCallInConference = hangUpCallInConference;
1115 this.gSetupConferenceTwoCalls = setupConferenceTwoCalls;
1116 this.gSetupConferenceThreeCalls = setupConferenceThreeCalls;
1117 this.gSetupConferenceFiveCalls = setupConferenceFiveCalls;
1118 this.gReceivedPending = receivedPending;
1119 }());
1121 function _startTest(permissions, test) {
1122 function permissionSetUp() {
1123 SpecialPowers.setBoolPref("dom.mozSettings.enabled", true);
1124 for (let per of permissions) {
1125 SpecialPowers.addPermission(per, true, document);
1126 }
1127 }
1129 function permissionTearDown() {
1130 SpecialPowers.clearUserPref("dom.mozSettings.enabled");
1131 for (let per of permissions) {
1132 SpecialPowers.removePermission(per, document);
1133 }
1134 }
1136 function setUp() {
1137 log("== Test SetUp ==");
1138 permissionSetUp();
1139 // Make sure that we get the telephony after adding permission.
1140 telephony = window.navigator.mozTelephony;
1141 ok(telephony);
1142 delayTelephonyDial();
1143 conference = telephony.conferenceGroup;
1144 ok(conference);
1145 return gClearCalls().then(gCheckInitialState);
1146 }
1148 // Extend finish() with tear down.
1149 finish = (function() {
1150 let originalFinish = finish;
1152 function tearDown() {
1153 log("== Test TearDown ==");
1154 restoreTelephonyDial();
1155 emulator.waitFinish()
1156 .then(permissionTearDown)
1157 .then(function() {
1158 originalFinish.apply(this, arguments);
1159 });
1160 }
1162 return tearDown.bind(this);
1163 }());
1165 function mainTest() {
1166 setUp()
1167 .then(function onSuccess() {
1168 log("== Test Start ==");
1169 test();
1170 }, function onError(error) {
1171 SpecialPowers.Cu.reportError(error);
1172 ok(false, "SetUp error");
1173 });
1174 }
1176 mainTest();
1177 }
1179 function startTest(test) {
1180 _startTest(["telephony"], test);
1181 }
1183 function startTestWithPermissions(permissions, test) {
1184 _startTest(permissions.concat("telephony"), test);
1185 }
1187 function startDSDSTest(test) {
1188 let numRIL;
1189 try {
1190 numRIL = SpecialPowers.getIntPref("ril.numRadioInterfaces");
1191 } catch (ex) {
1192 numRIL = 1; // Pref not set.
1193 }
1195 if (numRIL > 1) {
1196 startTest(test);
1197 } else {
1198 log("Not a DSDS environment. Test is skipped.");
1199 ok(true); // We should run at least one test.
1200 finish();
1201 }
1202 }