Skip to content

Commit 86f73f7

Browse files
martgilmartgil
and
martgil
authored
#5905 Add Secure Reply-All to Gmail context menu (#5909)
* Add UI change to add "Secure Reply All" * add comment + ready for incoming merge conflict * add checking before adding the button to avoid unncessary button * update gmail test * wip: safe attempt to fix issue * wip: add retryInterval whenever element is not yet rendered * wip: fix failing test * wip: update comment * wip:fix secure btn being injected for non-showing regular gmail button * fix delay in showing secure actions menu buttons * pr reviews: use .is(':visible') * wip: use element observer * wip: update Gmail test duration to 45min * wip: replace .ready() with shorthand jQuery(function() { }) * wip: cleanup * pr review: use native js code for faster execution --------- Co-authored-by: martgil <[email protected]>
1 parent cbc1687 commit 86f73f7

File tree

8 files changed

+57
-30
lines changed

8 files changed

+57
-30
lines changed

.semaphore/semaphore.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ blocks:
6161
run:
6262
when: branch = 'master' OR branch =~ 'live-test' OR branch =~ 'gmail-test'
6363
execution_time_limit:
64-
minutes: 45
64+
minutes: 60
6565
task:
6666
secrets:
6767
- name: flowcrypt-browser-ci-secrets

extension/img/svgs/reply-all-icon.svg

+1
Loading

extension/js/common/xss-safe-factory.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -269,9 +269,15 @@ export class XssSafeFactory {
269269
</div>`;
270270
};
271271

272-
public actionsMenuBtn = (action: 'reply' | 'forward') => {
273-
return `<div class="action_${action}_message_button action_menu_message_button" data-test="action-${action}-message-button">
274-
<img src="${this.srcImg(`svgs/${action}-icon.svg`)}" /><span>secure ${action}</span>
272+
public btnSecureMenuBtn = (replyOption: ReplyOption) => {
273+
const actionText = replyOption.replace('a_', '').replace('_', ' ');
274+
const action = {
275+
underscore: actionText.replace(' ', '_'),
276+
hyphen: actionText.replace(' ', '-'),
277+
};
278+
// * The action_${action.underscore}_message_button is used as an identifier in GmailElementReplacer.actionActivateSecureReplyHandler()
279+
return `<div class="action_${action.underscore}_message_button action_menu_message_button" data-test="action-${action.hyphen}-message-button">
280+
<img src="${this.srcImg(`svgs/${action.hyphen}-icon.svg`)}" /><span>secure ${actionText}</span>
275281
</div>`;
276282
};
277283

extension/js/content_scripts/webmail/generic/webmail-element-replacer.ts

-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ export abstract class WebmailElementReplacer {
1414
public abstract reinsertReplyBox: (replyMsgId: string) => void;
1515
public abstract scrollToReplyBox: (replyMsgId: string) => void;
1616
public abstract scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;
17-
public abstract addSecureActionsToMessageMenu: () => void;
1817

1918
public runIntervalFunctionsPeriodically = () => {
2019
const intervalFunctions = this.getIntervalFunctions();

extension/js/content_scripts/webmail/gmail/gmail-element-replacer.ts

+32-14
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ export class GmailElementReplacer extends WebmailElementReplacer {
7979
super();
8080
this.webmailCommon = new WebmailCommon(acctEmail, injector);
8181
this.pubLookup = new PubLookup(clientConfiguration);
82+
this.setupSecureActionsOnGmailMenu();
8283
}
8384

8485
public getIntervalFunctions = (): IntervalFunction[] => {
@@ -148,14 +149,14 @@ export class GmailElementReplacer extends WebmailElementReplacer {
148149
}
149150
};
150151

151-
public addSecureActionsToMessageMenu = () => {
152-
$(document).on('click', 'div.aHU.hx', event => {
153-
const $actionsBtn = $(event.currentTarget).find(this.sel.msgActionsBtn);
154-
if ($actionsBtn.length && !$('.action_menu_message_button').length) {
155-
this.addMenuButton('reply', '#r');
156-
this.addMenuButton('forward', '#r3');
152+
public setupSecureActionsOnGmailMenu = () => {
153+
const observer = new MutationObserver(() => {
154+
const gmailActionsMenu = document.querySelector(this.sel.msgActionsMenu);
155+
if (gmailActionsMenu && (gmailActionsMenu as HTMLElement).offsetParent !== undefined) {
156+
this.addSecureActionsToMessageMenu();
157157
}
158158
});
159+
observer.observe(document.body, { childList: true, subtree: true });
159160
};
160161

161162
private everything = () => {
@@ -291,13 +292,14 @@ export class GmailElementReplacer extends WebmailElementReplacer {
291292
return !!$('iframe.pgp_block').filter(':visible').length;
292293
};
293294

294-
private addMenuButton = (action: 'reply' | 'forward', selector: string) => {
295-
const gmailActionsMenuContainer = $(this.sel.msgActionsMenu).find(selector);
296-
const button = $(this.factory.actionsMenuBtn(action)).insertAfter(gmailActionsMenuContainer); // xss-safe-factory
297-
button.on(
298-
'click',
299-
Ui.event.handle((el, ev: JQuery.Event) => this.actionActivateSecureReplyHandler(el, ev))
300-
);
295+
private addMenuButton = (replyOption: ReplyOption, gmailContextMenuBtn: string) => {
296+
if ($(gmailContextMenuBtn).is(':visible')) {
297+
const button = $(this.factory.btnSecureMenuBtn(replyOption)).insertAfter(gmailContextMenuBtn); // xss-safe-factory
298+
button.on(
299+
'click',
300+
Ui.event.handle((el, ev: JQuery.Event) => this.actionActivateSecureReplyHandler(el, ev))
301+
);
302+
}
301303
};
302304

303305
private replaceConvoBtns = (force = false) => {
@@ -371,7 +373,14 @@ export class GmailElementReplacer extends WebmailElementReplacer {
371373
private actionActivateSecureReplyHandler = async (btn: HTMLElement, event: JQuery.Event) => {
372374
event.stopImmediatePropagation();
373375
const secureReplyInvokedFromMenu = btn.className.includes('action_menu_message_button');
374-
const replyOption: ReplyOption = btn.className.includes('reply') ? 'a_reply' : 'a_forward';
376+
let replyOption: ReplyOption;
377+
if (btn.className.includes('reply-all')) {
378+
replyOption = 'a_reply_all';
379+
} else if (btn.className.includes('forward')) {
380+
replyOption = 'a_forward';
381+
} else {
382+
replyOption = 'a_reply';
383+
}
375384
if ($('#switch_to_encrypted_reply').length) {
376385
$('#switch_to_encrypted_reply').trigger('click');
377386
return;
@@ -922,4 +931,13 @@ export class GmailElementReplacer extends WebmailElementReplacer {
922931
}
923932
}
924933
};
934+
935+
private addSecureActionsToMessageMenu = () => {
936+
if ($('.action_menu_message_button').length) {
937+
return;
938+
}
939+
this.addMenuButton('a_reply', '#r');
940+
this.addMenuButton('a_reply_all', '#r2');
941+
this.addMenuButton('a_forward', '#r3');
942+
};
925943
}

extension/js/content_scripts/webmail/gmail/gmail-webmail-startup.ts

-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ export class GmailWebmailStartup {
4545
const messageRenderer = await MessageRenderer.newInstance(acctEmail, new Gmail(acctEmail), relayManager, factory);
4646
this.replacer = new GmailElementReplacer(factory, clientConfiguration, acctEmail, messageRenderer, injector, notifications, relayManager);
4747
await notifications.showInitial(acctEmail);
48-
this.replacer.addSecureActionsToMessageMenu();
4948
this.replacer.runIntervalFunctionsPeriodically();
5049
};
5150

extension/manifest.json

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@
7878
"resources": [
7979
"/css/webmail.css",
8080
"/img/svgs/reply-icon.svg",
81+
"/img/svgs/reply-all-icon.svg",
8182
"/img/svgs/forward-icon.svg",
8283
"/img/svgs/spinner-white-small.svg",
8384
"/img/svgs/spinner-green-small.svg",

test/source/tests/gmail.ts

+13-10
Original file line numberDiff line numberDiff line change
@@ -512,22 +512,25 @@ export const defineGmailTests = (testVariant: TestVariant, testWithBrowser: Test
512512
await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail');
513513
const gmailPage = await openGmailPage(t, browser);
514514
await gotoGmailPage(gmailPage, '/FMfcgzGtwgfMhWTlgRwwKWzRhqNZzwXz'); // go to encrypted convo
515-
await Util.sleep(5);
516-
const actionsMenuSelector = '.J-J5-Ji.aap';
517-
await gmailPage.waitAndClick(actionsMenuSelector);
518-
await Util.sleep(3);
515+
const gmailContextMenu = '.J-J5-Ji.aap';
516+
await gmailPage.waitAndClick(gmailContextMenu);
517+
await Util.sleep(1);
519518
expect(await gmailPage.isElementPresent('@action-reply-message-button'));
520519
await gmailPage.waitAndClick('@action-reply-message-button');
521520
const replyBox = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
522-
await Util.sleep(3);
523521
await replyBox.waitForContent('@input-body', '');
524-
await gmailPage.waitAndClick(actionsMenuSelector);
525-
await Util.sleep(3);
522+
await gmailPage.waitAndClick(gmailContextMenu);
523+
await Util.sleep(1);
524+
expect(await gmailPage.isElementPresent('@action-reply-all-message-button'));
525+
await gmailPage.waitAndClick('@action-reply-all-message-button');
526+
const replyBox2 = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
527+
await replyBox2.waitForContent('@input-body', '');
528+
await gmailPage.waitAndClick(gmailContextMenu);
529+
await Util.sleep(1);
526530
expect(await gmailPage.isElementPresent('@action-forward-message-button'));
527531
await gmailPage.waitAndClick('@action-forward-message-button');
528-
const replyBox2 = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
529-
await Util.sleep(3);
530-
await replyBox2.waitForContent('@input-body', '---------- Forwarded message ---------');
532+
const replyBox3 = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
533+
await replyBox3.waitForContent('@input-body', '---------- Forwarded message ---------');
531534
})
532535
);
533536

0 commit comments

Comments
 (0)