This commit is contained in:
Phil Ringnalda 2014-11-27 17:43:48 -08:00
Родитель 822c385b60 8d8a729d78
Коммит c4c3cc0df2
59 изменённых файлов: 995 добавлений и 535 удалений

Просмотреть файл

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

Просмотреть файл

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

Просмотреть файл

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="6499615ecece69e726657dc5caaeefa05fbb66bf"/>

Просмотреть файл

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

Просмотреть файл

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d5d3f93914558b6f168447b805cd799c8233e300"/>

Просмотреть файл

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

Просмотреть файл

@ -17,7 +17,7 @@
</project>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="6499615ecece69e726657dc5caaeefa05fbb66bf"/>

Просмотреть файл

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "78d735b50d94254ff236fc34a6fbaa5ed27692a0",
"revision": "415520315b048f40979e9bac344bec99e18df901",
"repo_path": "integration/gaia-central"
}

Просмотреть файл

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>

Просмотреть файл

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

Просмотреть файл

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="fe893bb760a3bb64375f62fdf4762a58c59df9ef"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="6499615ecece69e726657dc5caaeefa05fbb66bf"/>

Просмотреть файл

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="80bc1445959db79e9d2e947cc56e1eb7b0d3d0f0"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="1fff49c664f905f11a86426a9835e6df6b58e825"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="50ad16a280fe9cfa0716f8c6ba16afdf7f266b49"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>

Просмотреть файл

@ -20,22 +20,52 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
/**
* Opens the panel for Loop and sizes it appropriately.
*
* @param {event} event The event opening the panel, used to anchor
* the panel to the button which triggers it.
* @param {event} event The event opening the panel, used to anchor
* the panel to the button which triggers it.
* @param {String} [tabId] Identifier of the tab to select when the panel is
* opened. Example: 'rooms', 'contacts', etc.
*/
openCallPanel: function(event) {
openCallPanel: function(event, tabId = null) {
let callback = iframe => {
// Helper function to show a specific tab view in the panel.
function showTab() {
if (!tabId) {
return;
}
let win = iframe.contentWindow;
let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
detail: {
action: "selectTab",
tab: tabId
}
}, win));
win.dispatchEvent(ev);
}
// If the panel has been opened and initialized before, we can skip waiting
// for the content to load - because it's already there.
if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
showTab();
return;
}
iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
injectLoopAPI(iframe.contentWindow);
iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
iframe.contentWindow.removeEventListener("loopPanelInitialized",
loopPanelInitialized);
showTab();
});
}, true);
};
// Used to clear the temporary "login" state from the button.
Services.obs.notifyObservers(null, "loop-status-changed", null);
PanelFrame.showPopup(window, event.target, "loop", null,
"about:looppanel", null, callback);
PanelFrame.showPopup(window, event ? event.target : this.toolbarButton.node,
"loop", null, "about:looppanel", null, callback);
},
/**
@ -89,6 +119,67 @@ XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/Panel
this.toolbarButton.node.setAttribute("state", state);
},
/**
* Show a desktop notification when 'do not disturb' isn't enabled.
*
* @param {Object} options Set of options that may tweak the appearance and
* behavior of the notification.
* Option params:
* - {String} title Notification title message
* - {String} [message] Notification body text
* - {String} [icon] Notification icon
* - {String} [sound] Sound to play
* - {String} [selectTab] Tab to select when the panel
* opens
* - {Function} [onclick] Callback to invoke when
* the notification is clicked.
* Opens the panel by default.
*/
showNotification: function(options) {
if (MozLoopService.doNotDisturb) {
return;
}
if (!options.title) {
throw new Error("Missing title, can not display notification");
}
let notificationOptions = {
body: options.message || ""
};
if (options.icon) {
notificationOptions.icon = options.icon;
}
if (options.sound) {
// This will not do anything, until bug bug 1105222 is resolved.
notificationOptions.mozbehavior = {
soundFile: `chrome://browser/content/loop/shared/sounds/${options.sound}.ogg`
};
}
let notification = new window.Notification(options.title, notificationOptions);
notification.addEventListener("click", e => {
if (window.closed) {
return;
}
try {
window.focus();
} catch (ex) {}
// We need a setTimeout here, otherwise the panel won't show after the
// window received focus.
window.setTimeout(() => {
if (typeof options.onclick == "function") {
options.onclick();
} else {
// Open the Loop panel as a default action.
this.openCallPanel(null, options.selectTab || null);
}
}, 0);
});
},
/**
* Play a sound in this window IF there's no sound playing yet.
*

Просмотреть файл

@ -5,7 +5,7 @@ let originalTab;
let newTab;
function isCurrent(tab, msg) {
const tolerance = 1;
const tolerance = 5;
const difference = Math.abs(Date.now() - tab.lastAccessed);
ok(difference <= tolerance, msg + " (difference: " + difference + ")");
}
@ -14,20 +14,20 @@ function test() {
waitForExplicitFinish();
originalTab = gBrowser.selectedTab;
setTimeout(step2, 100);
setTimeout(step2, 10);
}
function step2() {
isCurrent(originalTab, "selected tab has the current timestamp");
newTab = gBrowser.addTab("about:blank", {skipAnimation: true});
setTimeout(step3, 100);
setTimeout(step3, 10);
}
function step3() {
ok(newTab.lastAccessed < Date.now(), "new tab hasn't been selected so far");
gBrowser.selectedTab = newTab;
isCurrent(newTab, "new tab has the current timestamp after being selected");
setTimeout(step4, 100);
setTimeout(step4, 10);
}
function step4() {

Просмотреть файл

@ -93,7 +93,7 @@ const checkForParticipantsUpdate = function(room, updatedRoom) {
// Check for participants that joined.
for (participant of updatedRoom.participants) {
if (!containsParticipant(room, participant)) {
eventEmitter.emit("joined", room.roomToken, participant);
eventEmitter.emit("joined", room, participant);
eventEmitter.emit("joined:" + room.roomToken, participant);
}
}
@ -101,7 +101,7 @@ const checkForParticipantsUpdate = function(room, updatedRoom) {
// Check for participants that left.
for (participant of room.participants) {
if (!containsParticipant(updatedRoom, participant)) {
eventEmitter.emit("left", room.roomToken, participant);
eventEmitter.emit("left", room, participant);
eventEmitter.emit("left:" + room.roomToken, participant);
}
}

Просмотреть файл

@ -51,7 +51,7 @@ const cloneErrorObject = function(error, targetWindow) {
if (typeof value != "string" && typeof value != "number") {
value = String(value);
}
Object.defineProperty(Cu.waiveXrays(obj), prop, {
configurable: false,
enumerable: true,
@ -115,6 +115,8 @@ const injectObjectAPI = function(api, targetWindow) {
injectedAPI[func] = function(...params) {
let lastParam = params.pop();
let callbackIsFunction = (typeof lastParam == "function");
// Clone params coming from content to the current scope.
params = [cloneValueInto(p, api) for (p of params)];
// If the last parameter is a function, assume its a callback
// and wrap it differently.
@ -134,6 +136,7 @@ const injectObjectAPI = function(api, targetWindow) {
});
} else {
try {
lastParam = cloneValueInto(lastParam, api);
return cloneValueInto(api[func](...params, lastParam), targetWindow);
} catch (ex) {
return cloneValueInto(ex, targetWindow);

Просмотреть файл

@ -1030,7 +1030,7 @@ this.MozLoopService = {
};
LoopRooms.on("add", onRoomsChange);
LoopRooms.on("update", onRoomsChange);
LoopRooms.on("joined", (e, roomToken, participant) => {
LoopRooms.on("joined", (e, room, participant) => {
// Don't alert if we're in the doNotDisturb mode, or the participant
// is the owner - the content code deals with the rest of the sounds.
if (MozLoopServiceInternal.doNotDisturb || participant.owner) {
@ -1039,7 +1039,12 @@ this.MozLoopService = {
let window = gWM.getMostRecentWindow("navigator:browser");
if (window) {
window.LoopUI.playSound("room-joined");
window.LoopUI.showNotification({
sound: "room-joined",
title: room.roomName,
message: MozLoopServiceInternal.localizedStrings.get("rooms_room_joined_label"),
selectTab: "rooms"
});
}
});

Просмотреть файл

@ -759,6 +759,17 @@ loop.panel = (function(_, mozL10n) {
});
},
_UIActionHandler: function(e) {
switch (e.detail.action) {
case "selectTab":
this.selectTab(e.detail.tab);
break;
default:
console.error("Invalid action", e.detail.action);
break;
}
},
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be simplified.
@ -803,11 +814,13 @@ loop.panel = (function(_, mozL10n) {
componentDidMount: function() {
window.addEventListener("LoopStatusChanged", this._onStatusChanged);
window.addEventListener("GettingStartedSeen", this._gettingStartedSeen);
window.addEventListener("UIAction", this._UIActionHandler);
},
componentWillUnmount: function() {
window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen);
window.removeEventListener("UIAction", this._UIActionHandler);
},
_getUserDisplayName: function() {

Просмотреть файл

@ -759,6 +759,17 @@ loop.panel = (function(_, mozL10n) {
});
},
_UIActionHandler: function(e) {
switch (e.detail.action) {
case "selectTab":
this.selectTab(e.detail.tab);
break;
default:
console.error("Invalid action", e.detail.action);
break;
}
},
/**
* The rooms feature is hidden by default for now. Once it gets mainstream,
* this method can be simplified.
@ -803,11 +814,13 @@ loop.panel = (function(_, mozL10n) {
componentDidMount: function() {
window.addEventListener("LoopStatusChanged", this._onStatusChanged);
window.addEventListener("GettingStartedSeen", this._gettingStartedSeen);
window.addEventListener("UIAction", this._UIActionHandler);
},
componentWillUnmount: function() {
window.removeEventListener("LoopStatusChanged", this._onStatusChanged);
window.removeEventListener("GettingStartedSeen", this._gettingStartedSeen);
window.removeEventListener("UIAction", this._UIActionHandler);
},
_getUserDisplayName: function() {

Просмотреть файл

@ -239,11 +239,16 @@ loop.store = loop.store || {};
expiresIn: this.defaultExpiresIn
};
this._mozLoop.rooms.create(roomCreationData, function(err) {
this._mozLoop.rooms.create(roomCreationData, function(err, createdRoom) {
this.setStoreState({pendingCreation: false});
if (err) {
this.dispatchAction(new sharedActions.CreateRoomError({error: err}));
return;
}
// Opens the newly created room
this.dispatchAction(new sharedActions.OpenRoom({
roomToken: createdRoom.roomToken
}));
}.bind(this));
},

Просмотреть файл

@ -80,6 +80,7 @@ describe("loop.store.RoomStore", function () {
rooms: {
create: function() {},
getAll: function() {},
open: function() {},
on: sandbox.stub()
}
};
@ -230,13 +231,28 @@ describe("loop.store.RoomStore", function () {
it("should switch the pendingCreation state flag to false once the " +
"operation is done", function() {
sandbox.stub(fakeMozLoop.rooms, "create", function(data, cb) {
cb();
cb(null, {roomToken: "fakeToken"});
});
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
expect(store.getStoreState().pendingCreation).eql(false);
});
it("should dispatch an OpenRoom action once the operation is done",
function() {
var dispatch = sandbox.stub(dispatcher, "dispatch");
sandbox.stub(fakeMozLoop.rooms, "create", function(data, cb) {
cb(null, {roomToken: "fakeToken"});
});
store.createRoom(new sharedActions.CreateRoom(fakeRoomCreationData));
sinon.assert.calledOnce(dispatch);
sinon.assert.calledWithExactly(dispatch, new sharedActions.OpenRoom({
roomToken: "fakeToken"
}));
});
});
describe("#copyRoomUrl", function() {

Просмотреть файл

@ -146,25 +146,25 @@ const onRoomDeleted = function(e, room) {
gExpectedDeletes.splice(idx, 1);
}
const onRoomJoined = function(e, roomToken, participant) {
let participants = gExpectedJoins[roomToken];
const onRoomJoined = function(e, room, participant) {
let participants = gExpectedJoins[room.roomToken];
Assert.ok(participants, "Participant should be expected to join");
let idx = participants.indexOf(participant.roomConnectionId);
Assert.ok(idx > -1, "Participant should be expected to join");
participants.splice(idx, 1);
if (!participants.length) {
delete gExpectedJoins[roomToken];
delete gExpectedJoins[room.roomToken];
}
};
const onRoomLeft = function(e, roomToken, participant) {
let participants = gExpectedLeaves[roomToken];
const onRoomLeft = function(e, room, participant) {
let participants = gExpectedLeaves[room.roomToken];
Assert.ok(participants, "Participant should be expected to leave");
let idx = participants.indexOf(participant.roomConnectionId);
Assert.ok(idx > -1, "Participant should be expected to leave");
participants.splice(idx, 1);
if (!participants.length) {
delete gExpectedLeaves[roomToken];
delete gExpectedLeaves[room.roomToken];
}
};

Просмотреть файл

@ -609,17 +609,9 @@ Nfc.prototype = {
* Process a message from the gMessageManager.
*/
receiveMessage: function receiveMessage(message) {
let isRFAPI = message.name == "NFC:ChangeRFState";
let isSendFile = message.name == "NFC:SendFile";
let isInfoAPI = message.name == "NFC:QueryInfo";
if (!isRFAPI && !isInfoAPI && (this.rfState != NFC.NFC_RF_STATE_DISCOVERY)) {
debug("NFC is not enabled. current rfState:" + this.rfState);
this.sendNfcErrorResponse(message, NFC.NFC_GECKO_ERROR_NOT_ENABLED);
return null;
}
if (!isRFAPI && !isSendFile && !isInfoAPI) {
if (["NFC:ChangeRFState",
"NFC:SendFile",
"NFC:QueryInfo"].indexOf(message.name) == -1) {
// Update the current sessionId before sending to the NFC service.
message.data.sessionId = SessionHelper.getId(message.data.sessionToken);
}

Просмотреть файл

@ -26,14 +26,12 @@ this.DEBUG_NFC = DEBUG_ALL || false;
this.NFC_GECKO_SUCCESS = 0;
this.NFC_GECKO_ERROR_GENERIC_FAILURE = 1;
this.NFC_GECKO_ERROR_P2P_REG_INVALID = 2;
this.NFC_GECKO_ERROR_NOT_ENABLED = 3;
this.NFC_GECKO_ERROR_SEND_FILE_FAILED = 4;
this.NFC_GECKO_ERROR_BAD_SESSION_TOKEN = 5;
this.NFC_GECKO_ERROR_SEND_FILE_FAILED = 3;
this.NFC_GECKO_ERROR_BAD_SESSION_TOKEN = 4;
this.NFC_ERROR_MSG = {};
this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_GENERIC_FAILURE] = "NfcGenericFailureError";
this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_P2P_REG_INVALID] = "NfcP2PRegistrationInvalid";
this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_NOT_ENABLED] = "NfcNotEnabledError";
this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_SEND_FILE_FAILED] = "NfcSendFileFailed";
this.NFC_ERROR_MSG[this.NFC_GECKO_ERROR_BAD_SESSION_TOKEN] = "NfcBadSessionToken";

Просмотреть файл

@ -1514,14 +1514,17 @@ public abstract class GeckoApp
// External URLs should always be loaded regardless of whether Gecko is
// already running.
if (isExternalURL) {
// Restore tabs before opening an external URL so that the new tab
// is animated properly.
Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
loadStartupTab(passedUri);
} else if (!mIsRestoringActivity) {
loadStartupTab(null);
}
} else {
if (!mIsRestoringActivity) {
loadStartupTab(null);
}
// We now have tab stubs from the last session. Any future tabs should
// be animated.
Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
Tabs.getInstance().notifyListeners(null, Tabs.TabEvents.RESTORED);
}
// If we're not restoring, move the session file so it can be read for
// the last tabs section.

Просмотреть файл

@ -87,7 +87,6 @@ public class Tabs implements GeckoEventListener {
private Tabs() {
EventDispatcher.getInstance().registerGeckoThreadListener(this,
"Session:RestoreEnd",
"Tab:Added",
"Tab:Close",
"Tab:Select",
@ -410,11 +409,6 @@ public class Tabs implements GeckoEventListener {
public void handleMessage(String event, JSONObject message) {
Log.d(LOGTAG, "handleMessage: " + event);
try {
if (event.equals("Session:RestoreEnd")) {
notifyListeners(null, TabEvents.RESTORED);
return;
}
// All other events handled below should contain a tabID property
int id = message.getInt("tabID");
Tab tab = getTab(id);

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 242 B

После

Ширина:  |  Высота:  |  Размер: 159 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 822 B

После

Ширина:  |  Высота:  |  Размер: 589 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 669 B

После

Ширина:  |  Высота:  |  Размер: 404 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 775 B

После

Ширина:  |  Высота:  |  Размер: 771 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 137 B

После

Ширина:  |  Высота:  |  Размер: 133 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 353 B

После

Ширина:  |  Высота:  |  Размер: 231 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 148 B

После

Ширина:  |  Высота:  |  Размер: 130 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 593 B

После

Ширина:  |  Высота:  |  Размер: 456 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 408 B

После

Ширина:  |  Высота:  |  Размер: 299 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 555 B

После

Ширина:  |  Высота:  |  Размер: 551 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 272 B

После

Ширина:  |  Высота:  |  Размер: 192 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 282 B

После

Ширина:  |  Высота:  |  Размер: 170 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

После

Ширина:  |  Высота:  |  Размер: 754 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 806 B

После

Ширина:  |  Высота:  |  Размер: 475 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 139 B

После

Ширина:  |  Высота:  |  Размер: 135 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 417 B

После

Ширина:  |  Высота:  |  Размер: 240 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 470 B

После

Ширина:  |  Высота:  |  Размер: 209 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.6 KiB

После

Ширина:  |  Высота:  |  Размер: 1006 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 1.1 KiB

После

Ширина:  |  Высота:  |  Размер: 661 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 163 B

После

Ширина:  |  Высота:  |  Размер: 137 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 606 B

После

Ширина:  |  Высота:  |  Размер: 334 B

Просмотреть файл

@ -103,6 +103,9 @@ public class TabStrip extends ThemedLinearLayout {
public void onTabChanged(Tab tab, Tabs.TabEvents msg, Object data) {
switch (msg) {
case RESTORED:
tabStripView.restoreTabs();
break;
case ADDED:
tabStripView.addTab(tab);
break;

Просмотреть файл

@ -44,6 +44,8 @@ public class TabStripView extends TwoWayView {
private final TabAnimatorListener animatorListener;
private boolean isRestoringTabs;
// Filled by calls to ShapeDrawable.getPadding();
// saved to prevent allocation in draw().
private final Rect dividerPadding = new Rect();
@ -205,7 +207,46 @@ public class TabStripView extends TwoWayView {
});
}
private void animateRestoredTabs() {
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final List<Animator> childAnimators = new ArrayList<Animator>();
final int tabHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
childAnimators.add(
ObjectAnimator.ofFloat(child, "translationY", tabHeight, 0));
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.addListener(animatorListener);
TransitionsTracker.track(animatorSet);
animatorSet.start();
return true;
}
});
}
private void ensurePositionIsVisible(final int position) {
// We just want to move the strip to the right position
// when restoring tabs on startup.
if (isRestoringTabs) {
setSelection(position);
return;
}
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
@ -244,6 +285,13 @@ public class TabStripView extends TwoWayView {
adapter.clear();
}
void restoreTabs() {
isRestoringTabs = true;
refreshTabs();
animateRestoredTabs();
isRestoringTabs = false;
}
void addTab(Tab tab) {
// Refresh the list to make sure the new tab is
// added in the right position.

Просмотреть файл

@ -6,6 +6,7 @@
package org.mozilla.gecko.tabs;
import java.util.ArrayList;
import java.util.List;
import org.mozilla.gecko.animation.ViewHelper;
import org.mozilla.gecko.GeckoAppShell;
@ -18,13 +19,22 @@ import org.mozilla.gecko.Tabs;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.util.SparseArray;
import android.view.Gravity;
import android.view.View;
import android.widget.GridView;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.widget.Button;
import android.widget.GridView;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.PropertyValuesHolder;
import com.nineoldandroids.animation.ValueAnimator;
/**
* A tabs layout implementation for the tablet redesign (bug 1014156).
@ -36,12 +46,18 @@ class TabsGridLayout extends GridView
Tabs.OnTabsChangedListener {
private static final String LOGTAG = "Gecko" + TabsGridLayout.class.getSimpleName();
private static final int ANIM_TIME_MS = 200;
public static final int ANIM_DELAY_MULTIPLE_MS = 20;
private static final DecelerateInterpolator ANIM_INTERPOLATOR = new DecelerateInterpolator();
private final Context mContext;
private TabsPanel mTabsPanel;
private final SparseArray<PointF> mTabLocations = new SparseArray<PointF>();
final private boolean mIsPrivate;
private final TabsLayoutAdapter mTabsAdapter;
private final int mColumnWidth;
public TabsGridLayout(Context context, AttributeSet attrs) {
super(context, attrs, R.attr.tabGridLayoutViewStyle);
@ -67,9 +83,13 @@ class TabsGridLayout extends GridView
setGravity(Gravity.CENTER);
setNumColumns(GridView.AUTO_FIT);
// The clipToPadding setting in the styles.xml doesn't seem to be working (bug 1101784)
// so lets set it manually in code for the moment as it's needed for the padding animation
setClipToPadding(false);
final Resources resources = getResources();
final int columnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
setColumnWidth(columnWidth);
mColumnWidth = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_column_width);
setColumnWidth(mColumnWidth);
final int padding = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding);
final int paddingTop = resources.getDimensionPixelSize(R.dimen.new_tablet_tab_panel_grid_padding_top);
@ -87,9 +107,7 @@ class TabsGridLayout extends GridView
mCloseClickListener = new Button.OnClickListener() {
@Override
public void onClick(View v) {
TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
Tabs.getInstance().closeTab(tab);
closeTab(v);
}
};
@ -121,6 +139,47 @@ class TabsGridLayout extends GridView
}
}
private void populateTabLocations(final Tab removedTab) {
mTabLocations.clear();
final int firstPosition = getFirstVisiblePosition();
final int lastPosition = getLastVisiblePosition();
final int numberOfColumns = getNumColumns();
final int childCount = getChildCount();
final int removedPosition = mTabsAdapter.getPositionForTab(removedTab);
for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
final View child = getChildAt(i);
if (child != null) {
mTabLocations.append(x, new PointF(child.getX(), child.getY()));
}
}
final boolean firstChildOffScreen = ((firstPosition > 0) || getChildAt(0).getY() < 0);
final boolean lastChildVisible = (lastPosition - childCount == firstPosition - 1);
final boolean oneItemOnLastRow = (lastPosition % numberOfColumns == 0);
if (firstChildOffScreen && lastChildVisible && oneItemOnLastRow) {
// We need to set the view's bottom padding to prevent a sudden jump as the
// last item in the row is being removed. We then need to remove the padding
// via a sweet animation
final int removedHeight = getChildAt(0).getMeasuredHeight();
final int verticalSpacing = getVerticalSpacing();
ValueAnimator paddingAnimator = ValueAnimator.ofInt(getPaddingBottom() + removedHeight + verticalSpacing, getPaddingBottom());
paddingAnimator.setDuration(ANIM_TIME_MS * 2);
paddingAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (Integer) animation.getAnimatedValue());
}
});
paddingAnimator.start();
}
}
@Override
public void setTabsPanel(TabsPanel panel) {
mTabsPanel = panel;
@ -160,6 +219,9 @@ class TabsGridLayout extends GridView
break;
case CLOSED:
if(mTabsAdapter.getCount() > 0) {
animateRemoveTab(tab);
}
if (tab.isPrivate() == mIsPrivate && mTabsAdapter.getCount() > 0) {
if (mTabsAdapter.removeTab(tab)) {
int selected = mTabsAdapter.getPositionForTab(Tabs.getInstance().getSelectedTab());
@ -244,4 +306,90 @@ class TabsGridLayout extends GridView
}
}
}
private View getViewForTab(Tab tab) {
final int position = mTabsAdapter.getPositionForTab(tab);
return getChildAt(position - getFirstVisiblePosition());
}
void closeTab(View v) {
TabsLayoutItemView itemView = (TabsLayoutItemView) v.getTag();
Tab tab = Tabs.getInstance().getTab(itemView.getTabId());
Tabs.getInstance().closeTab(tab);
updateSelectedPosition();
}
private void animateRemoveTab(final Tab removedTab) {
final int removedPosition = mTabsAdapter.getPositionForTab(removedTab);
final View removedView = getViewForTab(removedTab);
// The removed position might not have a matching child view
// when it's not within the visible range of positions in the strip.
if (removedView == null) {
return;
}
final int removedHeight = removedView.getMeasuredHeight();
populateTabLocations(removedTab);
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
// We don't animate the removed child view (it just disappears)
// but we still need its size to animate all affected children
// within the visible viewport.
final int childCount = getChildCount();
final int firstPosition = getFirstVisiblePosition();
final int numberOfColumns = getNumColumns();
final List<Animator> childAnimators = new ArrayList<>();
PropertyValuesHolder translateX, translateY;
for (int x = 0, i = removedPosition - firstPosition ; i < childCount; i++, x++) {
final View child = getChildAt(i);
ObjectAnimator animator;
if (i % numberOfColumns == numberOfColumns - 1) {
// Animate X & Y
translateX = PropertyValuesHolder.ofFloat("translationX", -(mColumnWidth * numberOfColumns), 0);
translateY = PropertyValuesHolder.ofFloat("translationY", removedHeight, 0);
animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX, translateY);
} else {
// Just animate X
translateX = PropertyValuesHolder.ofFloat("translationX", mColumnWidth, 0);
animator = ObjectAnimator.ofPropertyValuesHolder(child, translateX);
}
animator.setStartDelay(x * ANIM_DELAY_MULTIPLE_MS);
childAnimators.add(animator);
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
// Set the starting position of the child views - because we are delaying the start
// of the animation, we need to prevent the items being drawn in their final position
// prior to the animation starting
for (int x = 1, i = (removedPosition - firstPosition) + 1; i < childCount; i++, x++) {
final View child = getChildAt(i);
final PointF targetLocation = mTabLocations.get(x+1);
if (targetLocation == null) {
continue;
}
child.setX(targetLocation.x);
child.setY(targetLocation.y);
}
return true;
}
});
}
}

Просмотреть файл

@ -142,11 +142,6 @@ SessionStore.prototype = {
selected: true
});
}
// Let Java know we're done restoring tabs so tabs added after this can be animated
Messaging.sendRequest({
type: "Session:RestoreEnd"
});
}.bind(this)
};
Services.obs.addObserver(restoreCleanup, "sessionstore-windows-restored", false);

Просмотреть файл

@ -1519,6 +1519,25 @@ nsHttpHandler::TimerCallback(nsITimer * aTimer, void * aClosure)
thisObject->mCapabilities &= ~NS_HTTP_ALLOW_PIPELINING;
}
static void
NormalizeLanguageTag(char *code)
{
bool is_region = false;
while (*code != '\0')
{
if (*code == '-') {
is_region = true;
} else {
if (is_region) {
*code = nsCRT::ToUpper(*code);
} else {
*code = nsCRT::ToLower(*code);
}
}
code++;
}
}
/**
* Allocates a C string into that contains a ISO 639 language list
* notated with HTTP "q" values for output with a HTTP Accept-Language
@ -1574,6 +1593,8 @@ PrepareAcceptLanguages(const char *i_AcceptLanguages, nsACString &o_AcceptLangua
*trim = '\0';
if (*token != '\0') {
NormalizeLanguageTag(token);
comma = count_n++ != 0 ? "," : ""; // delimiter if not first item
uint32_t u = QVAL_TO_UINT(q);

Просмотреть файл

@ -0,0 +1,32 @@
var testpath = "/bug1054739";
function run_test() {
let intlPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService).getBranch("intl.");
let oldAcceptLangPref = intlPrefs.getCharPref("accept_languages");
let testData = [
["de, en-US, en", "de,en-US;q=0.7,en;q=0.3"],
["de,en-us,en", "de,en-US;q=0.7,en;q=0.3"],
["en-US, en", "en-US,en;q=0.5"],
["EN-US;q=0.2, EN", "en-US,en;q=0.5"],
];
for (let i = 0; i < testData.length; i++) {
let acceptLangPref = testData[i][0];
let expectedHeader = testData[i][1];
intlPrefs.setCharPref("accept_languages", acceptLangPref);
let acceptLangHeader = setupChannel(testpath).getRequestHeader("Accept-Language");
equal(acceptLangHeader, expectedHeader);
}
intlPrefs.setCharPref("accept_languages", oldAcceptLangPref);
}
function setupChannel(path) {
let ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
let chan = ios.newChannel("http://localhost:4444" + path, "", null);
chan.QueryInterface(Ci.nsIHttpChannel);
return chan;
}

Просмотреть файл

@ -198,6 +198,7 @@ skip-if = bits != 32
[test_gzipped_206.js]
[test_head.js]
[test_header_Accept-Language.js]
[test_header_Accept-Language_case.js]
[test_headers.js]
[test_http_headers.js]
[test_httpauth.js]

Просмотреть файл

@ -37,68 +37,67 @@ MobileIdentityVerificationFlow.prototype = {
return Promise.reject(ERROR_INTERNAL_UNEXPECTED);
}
this.sessionToken = registerResult.msisdnSessionToken;
return this._doVerification();
// We save the timestamp of the start of the verification timeout to be
// able to provide to the UI the remaining time on each retry.
if (!this.timer) {
log.debug("Creating verification code timer");
this.timerCreation = Date.now();
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
VERIFICATIONCODE_TIMEOUT,
this.timer.TYPE_ONE_SHOT);
}
if (!this.verifyStrategy) {
return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
}
return this.verifyStrategy()
.then(() => {
return this._doVerification();
}, (reason) => {
this.verificationCodeDeferred.reject(reason);
});
}
)
},
_doVerification: function() {
log.debug("_doVerification");
// We save the timestamp of the start of the verification timeout to be
// able to provide to the UI the remaining time on each retry.
if (!this.timer) {
log.debug("Creating verification code timer");
this.timerCreation = Date.now();
this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timer.initWithCallback(this.onVerificationCodeTimeout.bind(this),
VERIFICATIONCODE_TIMEOUT,
this.timer.TYPE_ONE_SHOT);
}
if (!this.verifyStrategy) {
return Promise.reject(ERROR_INTERNAL_INVALID_VERIFICATION_FLOW);
}
this.verificationCodeDeferred = Promise.defer();
this.verifyStrategy()
.then(
() => {
// If the verification flow can be for an external phone number,
// we need to ask the user for the verification code.
// In that case we don't do a notification about the verification
// process being done until the user enters the verification code
// in the UI.
if (this.verificationOptions.external) {
let timeLeft = 0;
if (this.timer) {
timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
Date.now();
}
this.ui.verificationCodePrompt(this.retries,
VERIFICATIONCODE_TIMEOUT / 1000,
timeLeft / 1000)
.then(
(verificationCode) => {
if (!verificationCode) {
return this.verificationCodeDeferred.reject(
ERROR_INTERNAL_INVALID_PROMPT_RESULT);
}
// If the user got the verification code that means that the
// introduced phone number didn't belong to any of the inserted
// SIMs.
this.ui.verify();
this.verificationCodeDeferred.resolve(verificationCode);
}
);
} else {
this.ui.verify();
}
},
(reason) => {
this.verificationCodeDeferred.reject(reason);
// If the verification flow can be for an external phone number,
// we need to ask the user for the verification code.
// In that case we don't do a notification about the verification
// process being done until the user enters the verification code
// in the UI.
if (this.verificationOptions.external) {
let timeLeft = 0;
if (this.timer) {
timeLeft = this.timerCreation + VERIFICATIONCODE_TIMEOUT -
Date.now();
}
);
this.ui.verificationCodePrompt(this.retries,
VERIFICATIONCODE_TIMEOUT / 1000,
timeLeft / 1000)
.then(
(verificationCode) => {
if (!verificationCode) {
return this.verificationCodeDeferred.reject(
ERROR_INTERNAL_INVALID_PROMPT_RESULT);
}
// If the user got the verification code that means that the
// introduced phone number didn't belong to any of the inserted
// SIMs.
this.ui.verify();
this.verificationCodeDeferred.resolve(verificationCode);
}
);
} else {
this.ui.verify();
}
return this.verificationCodeDeferred.promise.then(
this.onVerificationCode.bind(this)
);
@ -145,8 +144,11 @@ MobileIdentityVerificationFlow.prototype = {
log.error("Retries left " + this.retries);
if (!this.retries) {
this.ui.error(ERROR_NO_RETRIES_LEFT);
this.timer.cancel();
this.timer = null;
return Promise.reject(ERROR_NO_RETRIES_LEFT);
}
this.ui.error(ERROR_INVALID_VERIFICATION_CODE);
this.verifying = false;
if (this.queuedTimeout) {
this.onVerificationCodeTimeout();

Просмотреть файл

@ -5,6 +5,8 @@ const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
"use strict";
const Cm = Components.manager;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -17,3 +19,444 @@ Cu.import("resource://gre/modules/Services.jsm");
Services.prefs.setCharPref("services.mobileid.server.uri",
"https://dummyurl.com");
}).call(this);
const DEBUG = false;
const GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion";
const GET_ASSERTION_RETURN_OK = "MobileId:GetAssertion:Return:OK";
const GET_ASSERTION_RETURN_KO = "MobileId:GetAssertion:Return:KO";
// === Globals ===
const ORIGIN = "app://afakeorigin";
const APP_ID = 1;
const PRINCIPAL = {
origin: ORIGIN,
appId: APP_ID
};
const PHONE_NUMBER = "+34666555444";
const ANOTHER_PHONE_NUMBER = "+44123123123";
const VERIFICATION_CODE = "123456";
const SESSION_TOKEN = "aSessionToken";
const ICC_ID = "aIccId";
const ANOTHER_ICC_ID = "anotherIccId";
const MNC = "aMnc";
const ANOTHER_MNC = "anotherMnc";
const MCC = "aMcc";
const ANOTHER_MCC = "anotherMcc";
const OPERATOR = "aOperator";
const ANOTHER_OPERATOR = "anotherOperator";
const RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: ICC_ID,
mcc: MCC,
mnc: MNC,
msisdn: PHONE_NUMBER,
operator: OPERATOR
}
},
voice: {
network: {
shortName: OPERATOR
},
roaming: false
},
data: {
network: {
shortName: OPERATOR
}
}
};
const ANOTHER_RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: ANOTHER_ICC_ID,
mcc: ANOTHER_MCC,
mnc: ANOTHER_MNC,
msisdn: ANOTHER_PHONE_NUMBER,
operator: ANOTHER_OPERATOR
}
},
voice: {
network: {
shortName: ANOTHER_OPERATOR
},
roaming: false
},
data: {
network: {
shortName: ANOTHER_OPERATOR
}
}
};
const INVALID_RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: null,
mcc: "",
mnc: "",
msisdn: "",
operator: ""
}
},
voice: {
network: {
shortName: ""
},
roaming: undefined
},
data: {
network: {
shortName: ""
}
}
};
const CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9."
// === Helpers ===
function addPermission(aAction) {
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(ORIGIN, null, null);
let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getAppCodebasePrincipal(uri, APP_ID, false);
let pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
pm.addFromPrincipal(_principal, MOBILEID_PERM, aAction);
}
function removePermission() {
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(ORIGIN, null, null);
let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getAppCodebasePrincipal(uri, APP_ID, false);
let pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
pm.removeFromPrincipal(_principal, MOBILEID_PERM);
}
// === Mocks ===
let Mock = function(aOptions) {
if (!aOptions) {
aOptions = {};
}
this._options = aOptions;
this._spied = {};
};
Mock.prototype = {
_: function(aMethod) {
DEBUG && do_print("_ " + aMethod + JSON.stringify(this._spied));
let self = this;
return {
callsLength: function(aNumberOfCalls) {
if (aNumberOfCalls == 0) {
do_check_eq(self._spied[aMethod], undefined);
return;
}
do_check_eq(self._spied[aMethod].length, aNumberOfCalls);
},
call: function(aCallNumber) {
return {
arg: function(aArgNumber, aValue) {
let _arg = self._spied[aMethod][aCallNumber - 1][aArgNumber - 1];
if (Array.isArray(aValue)) {
do_check_eq(_arg.length, aValue.length)
for (let i = 0; i < _arg.length; i++) {
do_check_eq(_arg[i], aValue[i]);
}
return;
}
if (typeof aValue === 'object') {
do_check_eq(JSON.stringify(_arg), JSON.stringify(aValue));
return;
}
do_check_eq(_arg, aValue);
}
}
}
}
},
_spy: function(aMethod, aArgs) {
DEBUG && do_print(aMethod + " - " + JSON.stringify(aArgs));
if (!this._spied[aMethod]) {
this._spied[aMethod] = [];
}
this._spied[aMethod].push(aArgs);
},
getSpiedCalls: function(aMethod) {
return this._spied[aMethod];
}
};
// UI Glue mock up.
let MockUi = function(aOptions) {
Mock.call(this, aOptions);
};
MockUi.prototype = {
__proto__: Mock.prototype,
_startFlowResult: {
phoneNumber: PHONE_NUMBER,
mcc: MNC
},
_verifyCodePromptResult: {
verificationCode: VERIFICATION_CODE
},
startFlow: function() {
this._spy("startFlow", arguments);
return Promise.resolve(this._options.startFlowResult ||
this._startFlowResult);
},
verificationCodePrompt: function() {
this._spy("verifyCodePrompt", arguments);
return Promise.resolve(this._options.verificationCodePromptResult ||
this._verifyCodePromptResult);
},
verify: function() {
this._spy("verify", arguments);
},
error: function() {
this._spy("error", arguments);
},
verified: function() {
this._spy("verified", arguments);
},
set oncancel(aCallback) {
},
set onresendcode(aCallback) {
}
};
// Credentials store mock up.
let MockCredStore = function(aOptions) {
Mock.call(this, aOptions);
};
MockCredStore.prototype = {
__proto__: Mock.prototype,
_getByOriginResult: null,
_getByMsisdnResult: null,
_getByIccIdResult: null,
getByOrigin: function() {
this._spy("getByOrigin", arguments);
let result = this._getByOriginResult;
if (this._options.getByOriginResult) {
if (Array.isArray(this._options.getByOriginResult)) {
result = this._options.getByOriginResult.length ?
this._options.getByOriginResult.shift() : null;
} else {
result = this._options.getByOriginResult;
}
}
return Promise.resolve(result);
},
getByMsisdn: function() {
this._spy("getByMsisdn", arguments);
return Promise.resolve(this._options.getByMsisdnResult ||
this._getByMsisdnResult);
},
getByIccId: function() {
this._spy("getByIccId", arguments);
return Promise.resolve(this._options.getByIccIdResult ||
this._getByIccIdResult);
},
add: function() {
this._spy("add", arguments);
return Promise.resolve();
},
setDeviceIccIds: function() {
this._spy("setDeviceIccIds", arguments);
return Promise.resolve();
},
removeOrigin: function() {
this._spy("removeOrigin", arguments);
return Promise.resolve();
},
delete: function() {
this._spy("delete", arguments);
return Promise.resolve();
}
};
// Client mock up.
let MockClient = function(aOptions) {
Mock.call(this, aOptions);
};
MockClient.prototype = {
__proto__: Mock.prototype,
_discoverResult: {
verificationMethods: ["sms/mt"],
verificationDetails: {
"sms/mt": {
mtSender: "123",
url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
}
}
},
_registerResult: {
msisdnSessionToken: SESSION_TOKEN
},
_smsMtVerifyResult: {},
_verifyCodeResult: {
msisdn: PHONE_NUMBER
},
_signResult: {
cert: CERTIFICATE
},
hawk: {
now: function() {
return Date.now();
}
},
discover: function() {
this._spy("discover", arguments);
return Promise.resolve(this._options.discoverResult ||
this._discoverResult);
},
register: function() {
this._spy("register", arguments);
return Promise.resolve(this._options.registerResult ||
this._registerResult);
},
smsMtVerify: function() {
this._spy("smsMtVerify", arguments);
return Promise.resolve(this._options.smsMtVerifyResult ||
this._smsMtVerifyResult);
},
verifyCode: function() {
this._spy("verifyCode", arguments);
if (this._options.verifyCodeError) {
let error = Array.isArray(this._options.verifyCodeError) ?
this._options.verifyCodeError.shift() :
this._options.verifyCodeError;
if (!this._options.verifyCodeError.length) {
this._options.verifyCodeError = null;
}
return Promise.reject(error);
}
return Promise.resolve(this._options.verifyCodeResult ||
this._verifyCodeResult);
},
sign: function() {
this._spy("sign", arguments);
if (this._options.signError) {
let error = Array.isArray(this._options.signError) ?
this._options.signError.shift() :
this._options.signError;
return Promise.reject(error);
}
return Promise.resolve(this._options.signResult || this._signResult);
}
};
// Override MobileIdentityUIGlue.
const kMobileIdentityUIGlueUUID = "{05df0566-ca8a-4ec7-bc76-78626ebfbe9a}";
const kMobileIdentityUIGlueContractID =
"@mozilla.org/services/mobileid-ui-glue;1";
// Save original factory.
/*const kMobileIdentityUIGlueFactory =
Cm.getClassObject(Cc[kMobileIdentityUIGlueContractID], Ci.nsIFactory);*/
let fakeMobileIdentityUIGlueFactory = {
createInstance: function(aOuter, aIid) {
return MobileIdentityUIGlue.QueryInterface(aIid);
}
};
// MobileIdentityUIGlue fake component.
let MobileIdentityUIGlue = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileIdentityUIGlue]),
};
(function registerFakeMobileIdentityUIGlue() {
Cm.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(Components.ID(kMobileIdentityUIGlueUUID),
"MobileIdentityUIGlue",
kMobileIdentityUIGlueContractID,
fakeMobileIdentityUIGlueFactory);
})();
// The tests rely on having an app registered. Otherwise, it will throw.
// Override XULAppInfo.
const XUL_APP_INFO_UUID = Components.ID("{84fdc459-d96d-421c-9bff-a8193233ae75}");
const XUL_APP_INFO_CONTRACT_ID = "@mozilla.org/xre/app-info;1";
let (XULAppInfo = {
vendor: "Mozilla",
name: "MobileIdTest",
ID: "{230de50e-4cd1-11dc-8314-0800200b9a66}",
version: "1",
appBuildID: "2007010101",
platformVersion: "",
platformBuildID: "2007010101",
inSafeMode: false,
logConsoleErrors: true,
OS: "XPCShell",
XPCOMABI: "noarch-spidermonkey",
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIXULAppInfo,
Ci.nsIXULRuntime,
])
}) {
let XULAppInfoFactory = {
createInstance: function (outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return XULAppInfo.QueryInterface(iid);
}
};
Cm.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(XUL_APP_INFO_UUID,
"XULAppInfo",
XUL_APP_INFO_CONTRACT_ID,
XULAppInfoFactory);
}

Просмотреть файл

@ -3,424 +3,16 @@
"use strict";
const Cm = Components.manager;
Cu.import("resource://gre/modules/Promise.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/MobileIdentityManager.jsm");
Cu.import("resource://gre/modules/MobileIdentityCommon.jsm");
const DEBUG = false;
const GET_ASSERTION_IPC_MSG = "MobileId:GetAssertion";
const GET_ASSERTION_RETURN_OK = "MobileId:GetAssertion:Return:OK";
const GET_ASSERTION_RETURN_KO = "MobileId:GetAssertion:Return:KO";
// === Globals ===
const ORIGIN = "app://afakeorigin";
const APP_ID = 1;
const PRINCIPAL = {
origin: ORIGIN,
appId: APP_ID
};
const PHONE_NUMBER = "+34666555444";
const ANOTHER_PHONE_NUMBER = "+44123123123";
const VERIFICATION_CODE = "123456";
const SESSION_TOKEN = "aSessionToken";
const ICC_ID = "aIccId";
const ANOTHER_ICC_ID = "anotherIccId";
const MNC = "aMnc";
const ANOTHER_MNC = "anotherMnc";
const MCC = "aMcc";
const ANOTHER_MCC = "anotherMcc";
const OPERATOR = "aOperator";
const ANOTHER_OPERATOR = "anotherOperator";
const RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: ICC_ID,
mcc: MCC,
mnc: MNC,
msisdn: PHONE_NUMBER,
operator: OPERATOR
}
},
voice: {
network: {
shortName: OPERATOR
},
roaming: false
},
data: {
network: {
shortName: OPERATOR
}
}
};
const ANOTHER_RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: ANOTHER_ICC_ID,
mcc: ANOTHER_MCC,
mnc: ANOTHER_MNC,
msisdn: ANOTHER_PHONE_NUMBER,
operator: ANOTHER_OPERATOR
}
},
voice: {
network: {
shortName: ANOTHER_OPERATOR
},
roaming: false
},
data: {
network: {
shortName: ANOTHER_OPERATOR
}
}
};
const INVALID_RADIO_INTERFACE = {
rilContext: {
iccInfo: {
iccid: null,
mcc: "",
mnc: "",
msisdn: "",
operator: ""
}
},
voice: {
network: {
shortName: ""
},
roaming: undefined
},
data: {
network: {
shortName: ""
}
}
};
const CERTIFICATE = "eyJhbGciOiJEUzI1NiJ9.eyJsYXN0QXV0aEF0IjoxNDA0NDY5NzkyODc3LCJ2ZXJpZmllZE1TSVNETiI6IiszMTYxNzgxNTc1OCIsInB1YmxpYy1rZXkiOnsiYWxnb3JpdGhtIjoiRFMiLCJ5IjoiNGE5YzkzNDY3MWZhNzQ3YmM2ZjMyNjE0YTg1MzUyZjY5NDcwMDdhNTRkMDAxMDY4OWU5ZjJjZjc0ZGUwYTEwZTRlYjlmNDk1ZGFmZTA0NGVjZmVlNDlkN2YwOGU4ODQyMDJiOTE5OGRhNWZhZWE5MGUzZjRmNzE1YzZjNGY4Yjc3MGYxZTU4YWZhNDM0NzVhYmFiN2VlZGE1MmUyNjk2YzFmNTljNzMzYjFlYzBhNGNkOTM1YWIxYzkyNzAxYjNiYTA5ZDRhM2E2MzNjNTJmZjE2NGYxMWY3OTg1YzlmZjY3ZThmZDFlYzA2NDU3MTdkMjBiNDE4YmM5M2YzYzVkNCIsInAiOiJmZjYwMDQ4M2RiNmFiZmM1YjQ1ZWFiNzg1OTRiMzUzM2Q1NTBkOWYxYmYyYTk5MmE3YThkYWE2ZGMzNGY4MDQ1YWQ0ZTZlMGM0MjlkMzM0ZWVlYWFlZmQ3ZTIzZDQ4MTBiZTAwZTRjYzE0OTJjYmEzMjViYTgxZmYyZDVhNWIzMDVhOGQxN2ViM2JmNGEwNmEzNDlkMzkyZTAwZDMyOTc0NGE1MTc5MzgwMzQ0ZTgyYTE4YzQ3OTMzNDM4Zjg5MWUyMmFlZWY4MTJkNjljOGY3NWUzMjZjYjcwZWEwMDBjM2Y3NzZkZmRiZDYwNDYzOGMyZWY3MTdmYzI2ZDAyZTE3IiwicSI6ImUyMWUwNGY5MTFkMWVkNzk5MTAwOGVjYWFiM2JmNzc1OTg0MzA5YzMiLCJnIjoiYzUyYTRhMGZmM2I3ZTYxZmRmMTg2N2NlODQxMzgzNjlhNjE1NGY0YWZhOTI5NjZlM2M4MjdlMjVjZmE2Y2Y1MDhiOTBlNWRlNDE5ZTEzMzdlMDdhMmU5ZTJhM2NkNWRlYTcwNGQxNzVmOGViZjZhZjM5N2Q2OWUxMTBiOTZhZmIxN2M3YTAzMjU5MzI5ZTQ4MjliMGQwM2JiYzc4OTZiMTViNGFkZTUzZTEzMDg1OGNjMzRkOTYyNjlhYTg5MDQxZjQwOTEzNmM3MjQyYTM4ODk1YzlkNWJjY2FkNGYzODlhZjFkN2E0YmQxMzk4YmQwNzJkZmZhODk2MjMzMzk3YSJ9LCJwcmluY2lwYWwiOiIwMzgxOTgyYS0xZTgzLTI1NjYtNjgzZS05MDRmNDA0NGM1MGRAbXNpc2RuLWRldi5zdGFnZS5tb3phd3MubmV0IiwiaWF0IjoxNDA0NDY5NzgyODc3LCJleHAiOjE0MDQ0OTEzOTI4NzcsImlzcyI6Im1zaXNkbi1kZXYuc3RhZ2UubW96YXdzLm5ldCJ9."
// === Helpers ===
function addPermission(aAction) {
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(ORIGIN, null, null);
let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getAppCodebasePrincipal(uri, APP_ID, false);
let pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
pm.addFromPrincipal(_principal, MOBILEID_PERM, aAction);
}
function removePermission() {
let uri = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
.newURI(ORIGIN, null, null);
let _principal = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager)
.getAppCodebasePrincipal(uri, APP_ID, false);
let pm = Cc["@mozilla.org/permissionmanager;1"]
.getService(Ci.nsIPermissionManager);
pm.removeFromPrincipal(_principal, MOBILEID_PERM);
}
// === Mocks ===
let Mock = function(aOptions) {
if (!aOptions) {
aOptions = {};
}
this._options = aOptions;
this._spied = {};
};
Mock.prototype = {
_: function(aMethod) {
DEBUG && do_print("_ " + aMethod + JSON.stringify(this._spied));
let self = this;
return {
callsLength: function(aNumberOfCalls) {
if (aNumberOfCalls == 0) {
do_check_eq(self._spied[aMethod], undefined);
return;
}
do_check_eq(self._spied[aMethod].length, aNumberOfCalls);
},
call: function(aCallNumber) {
return {
arg: function(aArgNumber, aValue) {
let _arg = self._spied[aMethod][aCallNumber - 1][aArgNumber - 1];
if (Array.isArray(aValue)) {
do_check_eq(_arg.length, aValue.length)
for (let i = 0; i < _arg.length; i++) {
do_check_eq(_arg[i], aValue[i]);
}
return;
}
if (typeof aValue === 'object') {
do_check_eq(JSON.stringify(_arg), JSON.stringify(aValue));
return;
}
do_check_eq(_arg, aValue);
}
}
}
}
},
_spy: function(aMethod, aArgs) {
DEBUG && do_print(aMethod + " - " + JSON.stringify(aArgs));
if (!this._spied[aMethod]) {
this._spied[aMethod] = [];
}
this._spied[aMethod].push(aArgs);
},
getSpiedCalls: function(aMethod) {
return this._spied[aMethod];
}
};
// UI Glue mock up.
let MockUi = function(aOptions) {
Mock.call(this, aOptions);
};
MockUi.prototype = {
__proto__: Mock.prototype,
_startFlowResult: {
phoneNumber: PHONE_NUMBER,
mcc: MNC
},
_verifyCodePromptResult: {
verificationCode: VERIFICATION_CODE
},
startFlow: function() {
this._spy("startFlow", arguments);
return Promise.resolve(this._options.startFlowResult ||
this._startFlowResult);
},
verificationCodePrompt: function() {
this._spy("verifyCodePrompt", arguments);
return Promise.resolve(this._options.verificationCodePromptResult ||
this._verifyCodePromptResult);
},
verify: function() {
this._spy("verify", arguments);
},
error: function() {
this._spy("error", arguments);
},
verified: function() {
this._spy("verified", arguments);
},
set oncancel(aCallback) {
},
set onresendcode(aCallback) {
}
};
// Save original credential store instance.
const kMobileIdentityCredStore = MobileIdentityManager.credStore;
// Credentials store mock up.
let MockCredStore = function(aOptions) {
Mock.call(this, aOptions);
};
MockCredStore.prototype = {
__proto__: Mock.prototype,
_getByOriginResult: null,
_getByMsisdnResult: null,
_getByIccIdResult: null,
getByOrigin: function() {
this._spy("getByOrigin", arguments);
let result = this._getByOriginResult;
if (this._options.getByOriginResult) {
if (Array.isArray(this._options.getByOriginResult)) {
result = this._options.getByOriginResult.length ?
this._options.getByOriginResult.shift() : null;
} else {
result = this._options.getByOriginResult;
}
}
return Promise.resolve(result);
},
getByMsisdn: function() {
this._spy("getByMsisdn", arguments);
return Promise.resolve(this._options.getByMsisdnResult ||
this._getByMsisdnResult);
},
getByIccId: function() {
this._spy("getByIccId", arguments);
return Promise.resolve(this._options.getByIccIdResult ||
this._getByIccIdResult);
},
add: function() {
this._spy("add", arguments);
return Promise.resolve();
},
setDeviceIccIds: function() {
this._spy("setDeviceIccIds", arguments);
return Promise.resolve();
},
removeOrigin: function() {
this._spy("removeOrigin", arguments);
return Promise.resolve();
},
delete: function() {
this._spy("delete", arguments);
return Promise.resolve();
}
};
// Save original client instance.
const kMobileIdentityClient = MobileIdentityManager.client;
// Client mock up.
let MockClient = function(aOptions) {
Mock.call(this, aOptions);
};
MockClient.prototype = {
__proto__: Mock.prototype,
_discoverResult: {
verificationMethods: ["sms/mt"],
verificationDetails: {
"sms/mt": {
mtSender: "123",
url: "https://msisdn.accounts.firefox.com/v1/msisdn/sms/mt/verify"
}
}
},
_registerResult: {
msisdnSessionToken: SESSION_TOKEN
},
_smsMtVerifyResult: {},
_verifyCodeResult: {
msisdn: PHONE_NUMBER
},
_signResult: {
cert: CERTIFICATE
},
hawk: {
now: function() {
return Date.now();
}
},
discover: function() {
this._spy("discover", arguments);
return Promise.resolve(this._options.discoverResult ||
this._discoverResult);
},
register: function() {
this._spy("register", arguments);
return Promise.resolve(this._options.registerResult ||
this._registerResult);
},
smsMtVerify: function() {
this._spy("smsMtVerify", arguments);
return Promise.resolve(this._options.smsMtVerifyResult ||
this._smsMtVerifyResult);
},
verifyCode: function() {
this._spy("verifyCode", arguments);
return Promise.resolve(this._options.verifyCodeResult ||
this._verifyCodeResult);
},
sign: function() {
this._spy("sign", arguments);
if (this._options.signError) {
let error = Array.isArray(this._options.signError) ?
this._options.signError.shift() :
this._options.signError;
return Promise.reject(error);
}
return Promise.resolve(this._options.signResult || this._signResult);
}
};
// The test rely on having an app registered. Otherwise, it will throw.
// Override XULAppInfo.
const XUL_APP_INFO_UUID = Components.ID("{84fdc459-d96d-421c-9bff-a8193233ae75}");
const XUL_APP_INFO_CONTRACT_ID = "@mozilla.org/xre/app-info;1";
let (XULAppInfo = {
vendor: "Mozilla",
name: "MobileIdTest",
ID: "{230de50e-4cd1-11dc-8314-0800200b9a66}",
version: "1",
appBuildID: "2007010101",
platformVersion: "",
platformBuildID: "2007010101",
inSafeMode: false,
logConsoleErrors: true,
OS: "XPCShell",
XPCOMABI: "noarch-spidermonkey",
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIXULAppInfo,
Ci.nsIXULRuntime,
])
}) {
let XULAppInfoFactory = {
createInstance: function (outer, iid) {
if (outer != null) {
throw Cr.NS_ERROR_NO_AGGREGATION;
}
return XULAppInfo.QueryInterface(iid);
}
};
Cm.QueryInterface(Ci.nsIComponentRegistrar)
.registerFactory(XUL_APP_INFO_UUID,
"XULAppInfo",
XUL_APP_INFO_CONTRACT_ID,
XULAppInfoFactory);
}
// === Global cleanup ===
function cleanup() {
MobileIdentityManager.credStore = kMobileIdentityCredStore;
MobileIdentityManager.client = kMobileIdentityClient;
@ -431,7 +23,6 @@ function cleanup() {
// Unregister mocks and restore original code.
do_register_cleanup(cleanup);
// === Tests ===
function run_test() {
run_next_test();

Просмотреть файл

@ -0,0 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Cu.import("resource://gre/modules/MobileIdentityVerificationFlow.jsm");
function verifyStrategy() {
return Promise.resolve();
}
function cleanupStrategy() {
}
function run_test() {
do_print("= Bug 1101444: Invalid verification code shouldn't restart " +
"verification flow =");
let client = new MockClient({
// This will emulate two invalid attempts. The third time it will work.
verifyCodeError: ["INVALID", "INVALID"]
});
let ui = new MockUi();
let verificationFlow = new MobileIdentityVerificationFlow({
external: true,
sessionToken: SESSION_TOKEN,
msisdn: PHONE_NUMBER
}, ui, client, verifyStrategy, cleanupStrategy);
verificationFlow.doVerification().then(() => {
// We should only do the registration process once. We only try registering
// again when the timeout fires, but not when we enter an invalid
// verification code.
client._("register").callsLength(1);
client._("verifyCode").callsLength(3);
// Because we do two invalid attempts, we should show the invalid code error twice.
ui._("error").callsLength(2);
});
do_test_finished();
};

Просмотреть файл

@ -1,7 +1,8 @@
[DEFAULT]
head = head.js
tail =
skip-if = toolkit == 'gonk'
[test_mobileid_manager.js]
skip-if = 1
[test_mobileid_client.js]
[test_mobileid_verification_flow.js]