Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/master' into issue-5133-fix-brok…
Browse files Browse the repository at this point in the history
…en-threading
  • Loading branch information
martgil committed Nov 30, 2024
2 parents 66384e2 + b88601d commit e59f3b8
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 10 deletions.
19 changes: 19 additions & 0 deletions extension/css/webmail.css
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,25 @@ body.cryptup_gmail .inserted div.reply_message_button {
}
}

body.cryptup_gmail div.action_menu_message_button {
display: flex;
align-items: center;
text-transform: capitalize;
cursor: pointer;
padding: 4px;
}

body.cryptup_gmail div.action_menu_message_button:hover {
background-color: #eee; /* mimic Gmail hover. uses exact color pallete from Gmail */
}

body.cryptup_gmail div.action_menu_message_button > img {
height: 20px;
width: 20px;
padding: 0 12px;
object-fit: contain;
}

body.cryptup_gmail.firefox .inserted div.reply_message_button {
padding-top: 16px;
}
Expand Down
26 changes: 26 additions & 0 deletions extension/img/svgs/forward-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions extension/js/common/xss-safe-factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,12 @@ export class XssSafeFactory {
this.destroyableCls
} reply_message_button" data-test="secure-reply-button" role="button" tabindex="0" data-tooltip="Secure Reply" aria-label="Secure Reply">
<img title="Secure Reply" src="${this.srcImg('svgs/reply-icon.svg')}" />
</div>`;
};

public actionsMenuBtn = (action: 'reply' | 'forward') => {
return `<div class="action_${action}_message_button action_menu_message_button" data-test="action-${action}-message-button">
<img src="${this.srcImg(`svgs/${action}-icon.svg`)}" /><span>secure ${action}</span>
</div>`;
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact [email protected] */

import { ReplyOption } from '../../../../chrome/elements/compose-modules/compose-reply-btn-popover-module';
import { ContentScriptWindow } from '../../../common/browser/browser-window';
import { notifyMurdered } from './setup-webmail-content-script';

Expand All @@ -9,7 +10,7 @@ export abstract class WebmailElementReplacer {
private replacePgpElsInterval: number;

public abstract getIntervalFunctions: () => IntervalFunction[];
public abstract setReplyBoxEditable: () => Promise<void>;
public abstract setReplyBoxEditable: (replyOption?: ReplyOption) => Promise<void>;
public abstract reinsertReplyBox: (replyMsgId: string) => void;
public abstract scrollToReplyBox: (replyMsgId: string) => void;
public abstract scrollToCursorInReplyBox: (replyMsgId: string, cursorOffsetTop: number) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export class GmailElementReplacer extends WebmailElementReplacer {
msgInner: 'div.a3s:visible:not(.undefined), .message_inner_body:visible',
msgInnerText: 'table.cf.An',
msgInnerContainingPgp: "div.a3s:not(.undefined):contains('" + PgpArmor.headers('null').begin + "')",
msgActionsBtn: '.J-J5-Ji.aap',
msgActionsMenu: '.b7.J-M',
attachmentsContainerOuter: 'div.hq.gt',
attachmentsContainerInner: 'div.aQH',
translatePrompt: '.adI, .wl4W9b',
Expand Down Expand Up @@ -85,10 +87,12 @@ export class GmailElementReplacer extends WebmailElementReplacer {
];
};

public setReplyBoxEditable = async () => {
public setReplyBoxEditable = async (replyOption?: ReplyOption) => {
const replyContainerIframe = $('.reply_message_iframe_container > iframe').last();
if (replyContainerIframe.length) {
$(replyContainerIframe).replaceWith(this.factory.embeddedReply(this.getLastMsgReplyParams(this.getConvoRootEl(replyContainerIframe[0])), true)); // xss-safe-value
$(replyContainerIframe).replaceWith(
this.factory.embeddedReply(this.getLastMsgReplyParams(this.getConvoRootEl(replyContainerIframe[0]), replyOption), true)
); // xss-safe-value
} else {
await this.replaceStandardReplyBox(undefined, true);
}
Expand Down Expand Up @@ -147,6 +151,7 @@ export class GmailElementReplacer extends WebmailElementReplacer {
this.replaceArmoredBlocks().catch(Catch.reportErr);
this.replaceAttachments().catch(Catch.reportErr);
this.replaceComposeDraftLinks();
this.replaceActionsMenu();
this.replaceConvoBtns();
this.replaceStandardReplyBox().catch(Catch.reportErr);
this.evaluateStandardComposeRecipients().catch(Catch.reportErr);
Expand Down Expand Up @@ -276,6 +281,22 @@ export class GmailElementReplacer extends WebmailElementReplacer {
return !!$('iframe.pgp_block').filter(':visible').length;
};

private addMenuButton = (action: 'reply' | 'forward', selector: string) => {
const gmailActionsMenuContainer = $(this.sel.msgActionsMenu).find(selector);
const button = $(this.factory.actionsMenuBtn(action)).insertAfter(gmailActionsMenuContainer); // xss-safe-factory
button.on(
'click',
Ui.event.handle((el, ev: JQuery.Event) => this.actionActivateSecureReplyHandler(el, ev))
);
};

private replaceActionsMenu = () => {
if ($('.action_menu_message_button').length <= 0) {
this.addMenuButton('reply', '#r');
this.addMenuButton('forward', '#r3');
}
};

private replaceConvoBtns = (force = false) => {
const convoUpperIconsContainer = $('div.hj:visible');
const convoUpperIcons = $('span.pYTkkf-JX-ank-Rtc0Jf');
Expand Down Expand Up @@ -346,20 +367,26 @@ export class GmailElementReplacer extends WebmailElementReplacer {

private actionActivateSecureReplyHandler = async (btn: HTMLElement, event: JQuery.Event) => {
event.stopImmediatePropagation();
const secureReplyInvokedFromMenu = btn.className.includes('action_menu_message_button');
const replyOption: ReplyOption = btn.className.includes('reply') ? 'a_reply' : 'a_forward';
if ($('#switch_to_encrypted_reply').length) {
$('#switch_to_encrypted_reply').trigger('click');
return;
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const messageContainer = $(btn.closest('.h7')!);
const messageContainer = secureReplyInvokedFromMenu ? $('.T-I-JO.T-I-Kq').closest('.h7') : $(btn.closest('.h7')!);
if (messageContainer.is(':last-child')) {
if (this.isEncrypted()) {
await this.setReplyBoxEditable();
await this.setReplyBoxEditable(replyOption);
} else {
await this.replaceStandardReplyBox(undefined, true);
}
} else {
this.insertEncryptedReplyBox(messageContainer);
this.insertEncryptedReplyBox(messageContainer, replyOption);
}
if (secureReplyInvokedFromMenu) {
$(this.sel.msgActionsBtn).removeClass('T-I-JO T-I-Kq');
$(this.sel.msgActionsMenu).hide();
}
};

Expand Down Expand Up @@ -589,18 +616,18 @@ export class GmailElementReplacer extends WebmailElementReplacer {
return from ? Str.parseEmail(from) : undefined;
};

private getLastMsgReplyParams = (convoRootEl: JQuery): FactoryReplyParams => {
return { replyMsgId: this.determineMsgId($(convoRootEl).find(this.sel.msgInner).last()) };
private getLastMsgReplyParams = (convoRootEl: JQuery, replyOption?: ReplyOption): FactoryReplyParams => {
return { replyMsgId: this.determineMsgId($(convoRootEl).find(this.sel.msgInner).last()), replyOption };
};

private getConvoRootEl = (anyInnerElement: HTMLElement) => {
return $(anyInnerElement).closest('div.if, div.aHU, td.Bu').first();
};

private insertEncryptedReplyBox = (messageContainer: JQuery<Element>) => {
private insertEncryptedReplyBox = (messageContainer: JQuery<Element>, replyOption: ReplyOption) => {
const msgIdElement = messageContainer.find('[data-legacy-message-id], [data-message-id]');
const msgId = msgIdElement.attr('data-legacy-message-id') || msgIdElement.attr('data-message-id');
const replyParams: FactoryReplyParams = { replyMsgId: msgId, removeAfterClose: true };
const replyParams: FactoryReplyParams = { replyMsgId: msgId, removeAfterClose: true, replyOption };
const secureReplyBoxXssSafe = /* xss-safe-factory */ `<div class="remove_borders reply_message_iframe_container inserted">${this.factory.embeddedReply(
replyParams,
true,
Expand Down
1 change: 1 addition & 0 deletions extension/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"resources": [
"/css/webmail.css",
"/img/svgs/reply-icon.svg",
"/img/svgs/forward-icon.svg",
"/img/svgs/spinner-white-small.svg",
"/img/svgs/spinner-green-small.svg",
"/img/svgs/unlock.svg",
Expand Down
25 changes: 25 additions & 0 deletions test/source/tests/gmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -457,6 +457,31 @@ export const defineGmailTests = (testVariant: TestVariant, testWithBrowser: Test
})
);

test(
'mail.google.com - secure reply and forward in dot menu',
testWithBrowser(async (t, browser) => {
await BrowserRecipe.setUpCommonAcct(t, browser, 'ci.tests.gmail');
const gmailPage = await openGmailPage(t, browser);
await gotoGmailPage(gmailPage, '/FMfcgzGtwgfMhWTlgRwwKWzRhqNZzwXz'); // go to encrypted convo
await Util.sleep(5);
const actionsMenuSelector = '.J-J5-Ji.aap';
await gmailPage.waitAndClick(actionsMenuSelector);
await Util.sleep(3);
expect(await gmailPage.isElementPresent('@action-reply-message-button'));
await gmailPage.waitAndClick('@action-reply-message-button');
const replyBox = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
await Util.sleep(3);
await replyBox.waitForContent('@input-body', '');
await gmailPage.waitAndClick(actionsMenuSelector);
await Util.sleep(3);
expect(await gmailPage.isElementPresent('@action-forward-message-button'));
await gmailPage.waitAndClick('@action-forward-message-button');
const replyBox2 = await gmailPage.getFrame(['/chrome/elements/compose.htm'], { sleep: 5 });
await Util.sleep(3);
await replyBox2.waitForContent('@input-body', '---------- Forwarded message ---------');
})
);

// convo-sensitive, draft-sensitive test
test.serial(
'mail.google.com - plain reply draft',
Expand Down

0 comments on commit e59f3b8

Please sign in to comment.