Bug 1733568 - Add ability for chat accounts to prompt about chat room invites. r=aleca

Differential Revision: https://phabricator.services.mozilla.com/D132197

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Martin Giger 2021-12-01 22:06:41 +00:00
Родитель 5fdf36be5f
Коммит 24389a8c5e
6 изменённых файлов: 457 добавлений и 9 удалений

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

@ -8,9 +8,11 @@ interface imIAccount;
interface nsIDOMWindow;
interface nsIWebProgress;
/* This interface is for use in the browser-request notification, to
let protocol plugins open a browser window. This is an unfortunate
necessity for protocols that require an OAuth authentication. */
/**
* This interface is for use in the browser-request notification, to
* let protocol plugins open a browser window. This is an unfortunate
* necessity for protocols that require an OAuth authentication.
*/
[scriptable, uuid(b89dbb38-0de4-11e0-b3d0-0002e304243c)]
interface prplIRequestBrowser: nsISupports {
readonly attribute AUTF8String promptText;
@ -20,12 +22,13 @@ interface prplIRequestBrowser: nsISupports {
in nsIWebProgress aWebProgress);
};
/* This interface is used for buddy authorization requests, when the
user needs to confirm if a remote contact should be allowed to see
his presence information. It is implemented by the aSubject
parameter of the buddy-authorization-request and
buddy-authorization-request-canceled notifications.
*/
/**
* This interface is used for buddy authorization requests, when the
* user needs to confirm if a remote contact should be allowed to see
* his presence information. It is implemented by the aSubject
* parameter of the buddy-authorization-request and
* buddy-authorization-request-canceled notifications.
*/
[scriptable, uuid(a55c1e24-17cc-4ddc-8c64-3bc315a3c3b1)]
interface prplIBuddyRequest: nsISupports {
readonly attribute imIAccount account;
@ -34,6 +37,26 @@ interface prplIBuddyRequest: nsISupports {
void deny();
};
/**
* This is used with chat room invitation requests, so the user can accept or
* reject an invitation. It is implemented by the aSubject parameter of the
* conv-authorization-request notification.
*/
[scriptable, uuid(44ac9606-711b-40f6-9031-94a9c60c938d)]
interface prplIChatRequest: nsISupports {
readonly attribute imIAccount account;
readonly attribute AUTF8String conversationName;
/**
* Resolves when the request is completed, with a boolean indicating if it
* was granted. Rejected if the request is cancelled.
*
* @type {Promise<boolean>}
*/
readonly attribute Promise completePromise;
void grant();
void deny();
};
/**
* Verification information for an encryption session (for example prplISession).
* Used to present a verification flow to the user.

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

@ -170,6 +170,7 @@ var GenericAccountPrototype = {
aConnectionErrorMessage
);
this.cancelPendingBuddyRequests();
this.cancelPendingChatRequests();
this.cancelPendingVerificationRequests();
},
@ -264,6 +265,86 @@ var GenericAccountPrototype = {
delete this._pendingBuddyRequests;
},
_pendingChatRequests: null,
addChatRequest(conversationName, grantCallback, denyCallback) {
if (!this._pendingChatRequests) {
this._pendingChatRequests = new Set();
}
let resolvePromise;
let rejectPromise;
let completePromise = new Promise((resolve, reject) => {
resolvePromise = resolve;
rejectPromise = reject;
});
/** @implements {prplIChatRequest} */
let chatRequest = {
get account() {
return this._account.imAccount;
},
get conversationName() {
return conversationName;
},
_account: this,
// Grant and deny callbacks both receive the auth request object as an
// argument for further use.
grant() {
resolvePromise(true);
grantCallback(this);
this._remove();
},
deny() {
resolvePromise(false);
denyCallback(this);
this._remove();
},
cancel() {
rejectPromise(new Error("Cancelled"));
this._remove();
},
completePromise,
_remove() {
this._account.removeChatRequest(this);
},
QueryInterface: ChromeUtils.generateQI(["prplIChatRequest"]),
};
this._pendingChatRequests.add(chatRequest);
Services.obs.notifyObservers(chatRequest, "conv-authorization-request");
},
removeChatRequest(aRequest) {
if (!this._pendingChatRequests) {
return;
}
this._pendingChatRequests.delete(aRequest);
},
/**
* Cancel a pending chat request.
*
* @param {string} conversationName - The conversation the request is for.
*/
cancelChatRequest(conversationName) {
if (!this._pendingChatRequests) {
return;
}
for (let request of this._pendingChatRequests) {
if (request.conversationName == conversationName) {
request.cancel();
break;
}
}
},
cancelPendingChatRequests() {
if (!this._pendingChatRequests) {
return;
}
for (let request of this._pendingChatRequests) {
request.cancel();
}
this._pendingChatRequests = null;
},
requestBuddyInfo(aBuddyName) {},
get canJoinChat() {

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

@ -1482,6 +1482,7 @@ var chatHandler = {
if (notification) {
notification.close();
}
return;
}
if (aTopic == "buddy-verification-request") {
aSubject.QueryInterface(Ci.imIIncomingSessionVerification);
@ -1556,6 +1557,53 @@ var chatHandler = {
if (notification) {
notification.close();
}
return;
}
if (aTopic == "conv-authorization-request") {
aSubject.QueryInterface(Ci.prplIChatRequest);
let value =
"conv-auth-request-" + aSubject.account.id + aSubject.conversationName;
let acceptButton = {
"l10n-id": "chat-conv-invite-accept",
callback() {
aSubject.grant();
},
};
let denyButton = {
"l10n-id": "chat-conv-invite-deny",
callback() {
aSubject.deny();
},
};
let box = this.msgNotificationBar;
// Remove the notification when the request is cancelled.
aSubject.completePromise.catch(() => {
let notification = box.getNotificationWithValue(value);
if (notification) {
notification.close();
}
});
let notification = box.appendNotification(
value,
{
label: "",
priority: box.PRIORITY_INFO_HIGH,
},
[acceptButton, denyButton]
);
document.l10n.setAttributes(
notification.messageText,
"chat-conv-invite-label",
{
conversation: aSubject.conversationName,
}
);
notification.removeAttribute("dismissable");
if (!gChatTab) {
let tabmail = document.getElementById("tabmail");
tabmail.openTab("chat", { background: true });
}
return;
}
if (aTopic == "conversation-update-type") {
// Find conversation in conversation list.
@ -1731,6 +1779,7 @@ var chatHandler = {
this._addObserver("buddy-authorization-request-canceled");
this._addObserver("buddy-verification-request");
this._addObserver("buddy-verification-request-canceled");
this._addObserver("conv-authorization-request");
let listbox = document.getElementById("contactlistbox");
listbox.addEventListener("keypress", function(aEvent) {
let item = listbox.selectedItem;

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

@ -17,4 +17,5 @@ head = head.js
[browser_logs.js]
[browser_messagesMail.js]
[browser_readMessage.js]
[browser_requestNotifications.js]
[browser_tooltips.js]

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

@ -0,0 +1,279 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
add_task(async function testGrantingBuddyRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
const notificationTopic = TestUtils.topicObserved(
"buddy-authorization-request"
);
const requestPromise = new Promise((resolve, reject) => {
prplAccount.addBuddyRequest("test-user", resolve, reject);
});
const [request] = await notificationTopic;
is(request.userName, "test-user");
is(request.account.id, account.id);
await TestUtils.waitForTick();
const notificationBox = window.chatHandler.msgNotificationBar;
const value = "buddy-auth-request-" + request.account.id + request.userName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
EventUtils.synthesizeMouseAtCenter(
notification.buttonContainer.firstElementChild,
{}
);
await requestPromise;
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});
add_task(async function testCancellingBuddyRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
const notificationTopic = TestUtils.topicObserved(
"buddy-authorization-request"
);
prplAccount.addBuddyRequest(
"test-user",
() => {
ok(false, "request was granted");
},
() => {
ok(false, "request was denied");
}
);
const [request] = await notificationTopic;
is(request.userName, "test-user");
is(request.account.id, account.id);
const notificationBox = window.chatHandler.msgNotificationBar;
const value = "buddy-auth-request-" + request.account.id + request.userName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
const cancelTopic = TestUtils.topicObserved(
"buddy-authorization-request-canceled"
);
prplAccount.cancelBuddyRequest("test-user");
const [canceledRequest] = await cancelTopic;
is(canceledRequest.userName, request.userName);
is(canceledRequest.account.id, request.account.id);
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});
add_task(async function testDenyingBuddyRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
const notificationTopic = TestUtils.topicObserved(
"buddy-authorization-request"
);
const requestPromise = new Promise((resolve, reject) => {
prplAccount.addBuddyRequest("test-user", reject, resolve);
});
const [request] = await notificationTopic;
is(request.userName, "test-user");
is(request.account.id, account.id);
const notificationBox = window.chatHandler.msgNotificationBar;
const value = "buddy-auth-request-" + request.account.id + request.userName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
EventUtils.synthesizeMouseAtCenter(
notification.buttonContainer.lastElementChild,
{}
);
await requestPromise;
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});
add_task(async function testGrantingChatRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
const requestTopic = TestUtils.topicObserved("conv-authorization-request");
const requestPromise = new Promise((resolve, reject) => {
prplAccount.addChatRequest("test-chat", resolve, reject);
});
const [request] = await requestTopic;
is(request.conversationName, "test-chat");
is(request.account.id, account.id);
const notificationBox = window.chatHandler.msgNotificationBar;
const value =
"conv-auth-request-" + request.account.id + request.conversationName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
ok(
BrowserTestUtils.is_hidden(notification.closeButton),
"Can't dismiss without interacting"
);
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
EventUtils.synthesizeMouseAtCenter(
notification.buttonContainer.firstElementChild,
{}
);
await requestPromise;
const result = await request.completePromise;
ok(result);
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});
add_task(async function testCancellingChatRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(
BrowserTestUtils.is_visible(document.getElementById("chatPanel")),
"chat tab visible"
);
const requestTopic = TestUtils.topicObserved("conv-authorization-request");
prplAccount.addChatRequest(
"test-chat",
() => {
ok(false, "chat request was granted");
},
() => {
ok(false, "chat request was denied");
}
);
const [request] = await requestTopic;
is(request.conversationName, "test-chat", "conversation name matches");
is(request.account.id, account.id, "account id matches");
const notificationBox = window.chatHandler.msgNotificationBar;
const value =
"conv-auth-request-" + request.account.id + request.conversationName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
prplAccount.cancelChatRequest("test-chat");
await Assert.rejects(
request.completePromise,
/Cancelled/,
"completePromise is rejected to indicate cancellation"
);
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});
add_task(async function testDenyingChatRequest() {
const account = Services.accounts.createAccount("testuser", "prpl-mochitest");
const prplAccount = account.prplAccount.wrappedJSObject;
account.password = "this is a test";
account.connect();
await openChatTab();
ok(BrowserTestUtils.is_visible(document.getElementById("chatPanel")));
const requestTopic = TestUtils.topicObserved("conv-authorization-request");
const requestPromise = new Promise((resolve, reject) => {
prplAccount.addChatRequest("test-chat", reject, resolve);
});
const [request] = await requestTopic;
is(request.conversationName, "test-chat");
is(request.account.id, account.id);
const notificationBox = window.chatHandler.msgNotificationBar;
const value =
"conv-auth-request-" + request.account.id + request.conversationName;
const notification = notificationBox.getNotificationWithValue(value);
ok(notification, "notification shown");
const closePromise = new Promise(resolve => {
notification.eventCallback = event => {
resolve();
};
});
EventUtils.synthesizeMouseAtCenter(
notification.buttonContainer.lastElementChild,
{}
);
await requestPromise;
const result = await request.completePromise;
ok(!result);
await closePromise;
ok(!notificationBox.getNotificationWithValue(value), "notification closed");
account.disconnect();
Services.accounts.deleteAccount(account.id);
});

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

@ -30,3 +30,18 @@ chat-identity-verified =
chat-buddy-identity-status = Encryption Trust
chat-buddy-identity-status-verified = Verified
chat-buddy-identity-status-unverified = Unverified
## Conversation invite notification box
# This string appears in a notification bar at the top of the Contacts window
# when someone invited the user to a multi user chat conversation, to request
# the user to confirm they want to join the chat.
# Variables:
# $conversation (String) - Name of the conversation the user is invited to.
chat-conv-invite-label = You have been invited to chat in { $conversation }
chat-conv-invite-accept =
.label = Accept
.accesskey = A
chat-conv-invite-deny =
.label = Reject
.accesskey = R