diff --git a/extension/chrome/elements/attachment.ts b/extension/chrome/elements/attachment.ts
index b1891a43a82..6cfea1bfa03 100644
--- a/extension/chrome/elements/attachment.ts
+++ b/extension/chrome/elements/attachment.ts
@@ -24,9 +24,8 @@ import { AttachmentWarnings } from './shared/attachment_warnings.js';
export class AttachmentDownloadView extends View {
public fesUrl?: string;
- public confirmationResultResolver?: (confirm: boolean) => void;
+ public readonly parentTabId: string;
protected readonly acctEmail: string;
- protected readonly parentTabId: string;
protected readonly frameId: string;
protected readonly origNameBasedOnFilename: string;
protected readonly isEncrypted: boolean;
@@ -85,8 +84,8 @@ export class AttachmentDownloadView extends View {
this.gmail = new Gmail(this.acctEmail);
}
- public getParentTabId = () => {
- return this.parentTabId;
+ public getDest = () => {
+ return this.tabId;
};
public render = async () => {
@@ -151,7 +150,7 @@ export class AttachmentDownloadView extends View {
this.ppChangedPromiseCancellation = { cancel: false }; // set to a new, not yet used object
}
});
- BrowserMsg.addListener('confirmation_result', CommonHandlers.createConfirmationResultHandler(this));
+ BrowserMsg.addListener('confirmation_result', CommonHandlers.createAsyncResultHandler());
BrowserMsg.listen(this.tabId);
};
diff --git a/extension/chrome/elements/pgp_block.ts b/extension/chrome/elements/pgp_block.ts
index 79ed3dc844a..b72ca934bb5 100644
--- a/extension/chrome/elements/pgp_block.ts
+++ b/extension/chrome/elements/pgp_block.ts
@@ -4,7 +4,7 @@
import { Url } from '../../js/common/core/common.js';
import { Assert } from '../../js/common/assert.js';
-import { RenderMessage, RenderMessageWithFrameId } from '../../js/common/render-message.js';
+import { RenderMessage } from '../../js/common/render-message.js';
import { Attachment } from '../../js/common/core/attachment.js';
import { Xss } from '../../js/common/platform/xss.js';
import { PgpBlockViewAttachmentsModule } from './pgp_block_modules/pgp-block-attachmens-module.js';
@@ -12,9 +12,9 @@ import { PgpBlockViewErrorModule } from './pgp_block_modules/pgp-block-error-mod
import { PgpBlockViewPrintModule } from './pgp_block_modules/pgp-block-print-module.js';
import { PgpBlockViewQuoteModule } from './pgp_block_modules/pgp-block-quote-module.js';
import { PgpBlockViewRenderModule } from './pgp_block_modules/pgp-block-render-module.js';
-import { Ui } from '../../js/common/browser/ui.js';
+import { CommonHandlers, Ui } from '../../js/common/browser/ui.js';
import { View } from '../../js/common/view.js';
-import { Bm } from '../../js/common/browser/browser-msg.js';
+import { BrowserMsg } from '../../js/common/browser/browser-msg.js';
export class PgpBlockView extends View {
public readonly acctEmail: string; // needed for attachment decryption, probably should be refactored out
@@ -27,6 +27,7 @@ export class PgpBlockView extends View {
public readonly errorModule: PgpBlockViewErrorModule;
public readonly renderModule: PgpBlockViewRenderModule;
public readonly printModule = new PgpBlockViewPrintModule();
+ private tabId!: string;
public constructor() {
super();
@@ -41,21 +42,14 @@ export class PgpBlockView extends View {
this.quoteModule = new PgpBlockViewQuoteModule(this);
this.errorModule = new PgpBlockViewErrorModule(this);
this.renderModule = new PgpBlockViewRenderModule(this);
- chrome.runtime.onMessage.addListener((message: Bm.Raw) => {
- if (message.name === 'pgp_block_render') {
- const msg = message.data.bm as RenderMessageWithFrameId;
- if (msg.frameId === this.frameId) {
- this.processMessage(msg);
- return true;
- }
- }
- return false;
- });
- window.addEventListener('load', () => window.parent.postMessage({ readyToReceive: this.frameId }, '*'));
}
+ public getDest = () => {
+ return this.tabId;
+ };
+
public render = async () => {
- //
+ this.tabId = await BrowserMsg.requiredTabId();
};
public setHandlers = () => {
@@ -63,6 +57,12 @@ export class PgpBlockView extends View {
'click',
this.setHandler(() => this.printModule.printPGPBlock())
);
+ BrowserMsg.addListener('pgp_block_render', async (msg: RenderMessage) => {
+ this.processMessage(msg);
+ });
+ BrowserMsg.addListener('confirmation_result', CommonHandlers.createAsyncResultHandler());
+ BrowserMsg.listen(this.tabId);
+ BrowserMsg.send.pgpBlockReady({ frameId: this.frameId, messageSender: this.tabId });
};
private processMessage = (data: RenderMessage) => {
diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts
index 20ddc65c542..656073ee207 100644
--- a/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts
+++ b/extension/chrome/elements/pgp_block_modules/pgp-block-attachmens-module.ts
@@ -7,7 +7,7 @@ import { Attachment } from '../../../js/common/core/attachment.js';
import { Browser } from '../../../js/common/browser/browser.js';
import { BrowserMsg } from '../../../js/common/browser/browser-msg.js';
import { PgpBlockView } from '../pgp_block.js';
-import { CommonHandlers, Ui } from '../../../js/common/browser/ui.js';
+import { Ui } from '../../../js/common/browser/ui.js';
import { Xss } from '../../../js/common/platform/xss.js';
import { KeyStore } from '../../../js/common/platform/store/key-store.js';
import { XssSafeFactory } from '../../../js/common/xss-safe-factory.js';
@@ -21,10 +21,6 @@ export class PgpBlockViewAttachmentsModule {
public constructor(private view: PgpBlockView) {}
- public getParentTabId = () => {
- return this.view.parentTabId;
- };
-
public renderInnerAttachments = (attachments: Attachment[], isEncrypted: boolean) => {
Xss.sanitizeAppend('#pgp_block', '
');
this.includedAttachments = attachments;
@@ -78,8 +74,6 @@ export class PgpBlockViewAttachmentsModule {
}
})
);
- BrowserMsg.addListener('confirmation_result', CommonHandlers.createConfirmationResultHandler(this));
- BrowserMsg.listen(this.view.parentTabId);
};
private previewAttachmentClickedHandler = async (attachment: Attachment) => {
@@ -102,7 +96,7 @@ export class PgpBlockViewAttachmentsModule {
type: encrypted.type,
data: decrypted.content,
});
- if (await AttachmentWarnings.confirmSaveToDownloadsIfNeeded(attachment, this)) {
+ if (await AttachmentWarnings.confirmSaveToDownloadsIfNeeded(attachment, this.view)) {
Browser.saveToDownloads(attachment);
}
} else {
diff --git a/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts b/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts
index 200dd957442..ed541b52fa2 100644
--- a/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts
+++ b/extension/chrome/elements/pgp_block_modules/pgp-block-render-module.ts
@@ -145,7 +145,7 @@ export class PgpBlockViewRenderModule {
public renderSignatureOffline = () => {
this.renderSignatureStatus('error verifying signature: offline, click to retry').on(
'click',
- this.view.setHandler(() => window.parent.postMessage({ retry: this.view.frameId }, '*'))
+ this.view.setHandler(() => BrowserMsg.send.pgpBlockRetry({ frameId: this.view.frameId, messageSender: this.view.getDest() }))
);
};
}
diff --git a/extension/chrome/settings/inbox/inbox-modules/inbox-active-thread-module.ts b/extension/chrome/settings/inbox/inbox-modules/inbox-active-thread-module.ts
index 4c83aca3b98..bd5ecea1868 100644
--- a/extension/chrome/settings/inbox/inbox-modules/inbox-active-thread-module.ts
+++ b/extension/chrome/settings/inbox/inbox-modules/inbox-active-thread-module.ts
@@ -189,7 +189,7 @@ export class InboxActiveThreadModule extends ViewModule {
loaderContext.getRenderedMessageXssSafe() +
loaderContext.getRenderedAttachmentsXssSafe();
$('.thread').append(this.wrapMsg(htmlId, r)); // xss-safe-value
- await this.view.messageRenderer.startProcessingInlineBlocks(this.view.relayManager, this.view.factory, messageInfo, blocksInFrames);
+ this.view.messageRenderer.startProcessingInlineBlocks(this.view.relayManager, this.view.factory, messageInfo, blocksInFrames);
if (exportBtn) {
$('.action-export').on(
'click',
diff --git a/extension/chrome/settings/inbox/inbox.ts b/extension/chrome/settings/inbox/inbox.ts
index c1596a5db91..5ffdd4b71c7 100644
--- a/extension/chrome/settings/inbox/inbox.ts
+++ b/extension/chrome/settings/inbox/inbox.ts
@@ -24,7 +24,6 @@ import { XssSafeFactory } from '../../../js/common/xss-safe-factory.js';
import { AcctStore } from '../../../js/common/platform/store/acct-store.js';
import { RelayManager } from '../../../js/common/relay-manager.js';
import { MessageRenderer } from '../../../js/common/message-renderer.js';
-import { Env } from '../../../js/common/browser/env.js';
export class InboxView extends View {
public readonly inboxMenuModule: InboxMenuModule;
@@ -62,16 +61,11 @@ export class InboxView extends View {
this.inboxNotificationModule = new InboxNotificationModule(this);
this.inboxActiveThreadModule = new InboxActiveThreadModule(this);
this.inboxListThreadsModule = new InboxListThreadsModule(this);
- window.addEventListener('message', e => {
- if (e.origin === Env.getExtensionOrigin()) {
- this.relayManager.handleMessageFromFrame(e.data);
- }
- });
}
public render = async () => {
this.tabId = await BrowserMsg.requiredTabId();
- this.relayManager = new RelayManager(this.tabId, this.debug);
+ this.relayManager = new RelayManager(this.debug);
this.factory = new XssSafeFactory(this.acctEmail, this.tabId);
this.injector = new Injector('settings', undefined, this.factory);
this.webmailCommon = new WebmailCommon(this.acctEmail, this.injector);
@@ -105,7 +99,9 @@ export class InboxView extends View {
public setHandlers = () => {
// BrowserMsg.addPgpListeners(); // todo - re-allow when https://github.com/FlowCrypt/flowcrypt-browser/issues/2560 fixed
+ this.addBrowserMsgListeners();
BrowserMsg.listen(this.tabId);
+ BrowserMsg.listenForWindowMessages(); // listen for direct messages from child iframes
Catch.setHandledInterval(this.webmailCommon.addOrRemoveEndSessionBtnIfNeeded, 30000);
$('.action_open_settings').on(
'click',
@@ -122,7 +118,6 @@ export class InboxView extends View {
'click',
this.setHandlerPrevent('double', async () => await Settings.newGoogleAcctAuthPromptThenAlertOrForward(this.tabId))
);
- this.addBrowserMsgListeners();
};
public redirectToUrl = (params: UrlParams) => {
@@ -182,6 +177,12 @@ export class InboxView extends View {
await Ui.modal.attachmentPreview(iframeUrl);
});
BrowserMsg.addListener('confirmation_show', CommonHandlers.showConfirmationHandler);
+ BrowserMsg.addListener('pgp_block_ready', async ({ frameId, messageSender }: Bm.PgpBlockReady) => {
+ this.relayManager.associate(frameId, messageSender);
+ });
+ BrowserMsg.addListener('pgp_block_retry', async ({ frameId, messageSender }: Bm.PgpBlockRetry) => {
+ this.relayManager.retry(frameId, messageSender);
+ });
if (this.debug) {
BrowserMsg.addListener('open_compose_window', async ({ draftId }: Bm.ComposeWindowOpenDraft) => {
console.log('received open_compose_window');
diff --git a/extension/js/background_page/bg-handlers.ts b/extension/js/background_page/bg-handlers.ts
index 59dc42fb99c..07bf12c3cdd 100644
--- a/extension/js/background_page/bg-handlers.ts
+++ b/extension/js/background_page/bg-handlers.ts
@@ -30,7 +30,7 @@ export class BgHandlers {
};
public static ajaxHandler = async (r: Bm.Ajax, sender: Bm.Sender): Promise => {
- if (r.req.context?.frameId) {
+ if (r.req.context?.operationId) {
// progress updates were requested via messages
let dest = r.req.context.tabId;
if (typeof dest === 'undefined') {
@@ -43,10 +43,10 @@ export class BgHandlers {
}
if (typeof dest !== 'undefined') {
const destination = dest;
- const frameId = r.req.context.frameId;
+ const operationId = r.req.context.operationId;
const expectedTransferSize = r.req.context.expectedTransferSize;
r.req.xhr = Api.getAjaxProgressXhrFactory({
- download: (percent, loaded, total) => BrowserMsg.send.ajaxProgress(destination, { percent, loaded, total, expectedTransferSize, frameId }),
+ download: (percent, loaded, total) => BrowserMsg.send.ajaxProgress(destination, { percent, loaded, total, expectedTransferSize, operationId }),
});
}
}
@@ -91,11 +91,15 @@ export class BgHandlers {
});
});
- public static respondWithSenderTabId = async (r: unknown, sender: Bm.Sender): Promise => {
+ public static respondWithSenderTabId = async (r: Bm._tab_, sender: Bm.Sender): Promise => {
if (sender === 'background') {
return { tabId: null }; // eslint-disable-line no-null/no-null
- } else if (sender.tab) {
- return { tabId: `${sender.tab.id}:${sender.frameId}` };
+ } else if (typeof sender.tab?.id === 'number' && sender.tab.id > 0) {
+ const tabId = `${sender.tab.id}:${sender.frameId}`;
+ if (r.contentScript) {
+ BrowserMsg.contentScriptsRegistry.add(tabId);
+ }
+ return { tabId };
} else {
// sender.tab: "This property will only be present when the connection was opened from a tab (including content scripts)"
// https://developers.chrome.com/extensions/runtime#type-MessageSender
diff --git a/extension/js/common/api/email-provider/gmail/google.ts b/extension/js/common/api/email-provider/gmail/google.ts
index f55bc7cf2f4..903024c4e9f 100644
--- a/extension/js/common/api/email-provider/gmail/google.ts
+++ b/extension/js/common/api/email-provider/gmail/google.ts
@@ -40,7 +40,7 @@ export class Google {
// eslint-disable-next-line @typescript-eslint/naming-convention
const headers = { Authorization: await GoogleAuth.googleApiAuthHeader(acctEmail) };
const context =
- 'frameId' in progress ? { frameId: progress.frameId, expectedTransferSize: progress.expectedTransferSize, tabId: progress.tabId } : undefined;
+ 'operationId' in progress ? { operationId: progress.operationId, expectedTransferSize: progress.expectedTransferSize, tabId: progress.tabId } : undefined;
const xhr = Api.getAjaxProgressXhrFactory('download' in progress || 'upload' in progress ? progress : {});
const request = { xhr, context, url, method, data, headers, crossDomain: true, contentType, async: true };
return (await GoogleAuth.apiGoogleCallRetryAuthErrorOneTime(acctEmail, request)) as RT;
diff --git a/extension/js/common/api/shared/api.ts b/extension/js/common/api/shared/api.ts
index cd7a5ecd954..a2f33292432 100644
--- a/extension/js/common/api/shared/api.ts
+++ b/extension/js/common/api/shared/api.ts
@@ -25,7 +25,7 @@ type RawAjaxErr = {
status?: number;
statusText?: string;
};
-export type ProgressDestFrame = { frameId: string; expectedTransferSize: number; tabId?: string };
+export type ProgressDestFrame = { operationId: string; expectedTransferSize: number; tabId?: string };
export type ApiCallContext = ProgressDestFrame | undefined;
export type ChunkedCb = (r: ProviderContactsResults) => Promise;
diff --git a/extension/js/common/browser/browser-msg.ts b/extension/js/common/browser/browser-msg.ts
index cf9b99bc78f..0282f55bebe 100644
--- a/extension/js/common/browser/browser-msg.ts
+++ b/extension/js/common/browser/browser-msg.ts
@@ -18,7 +18,7 @@ import { BrowserMsgCommonHandlers } from './browser-msg-common-handlers.js';
import { Browser } from './browser.js';
import { Env } from './env.js';
import { Time } from './time.js';
-import { RenderMessageWithFrameId } from '../render-message.js';
+import { RenderMessage } from '../render-message.js';
export type GoogleAuthWindowResult$result = 'Success' | 'Denied' | 'Error' | 'Closed';
@@ -34,6 +34,10 @@ export namespace Bm {
uid: string;
stack: string;
};
+ export type RawWindowMessageWithSender = Raw & {
+ data: { bm: AnyRequest & { messageSender: Dest }; objUrls: Dict };
+ responseName?: string;
+ };
export type SetCss = { css: Dict; traverseUp?: number; selector: string };
export type AddOrRemoveClass = { class: string; selector: string };
@@ -60,9 +64,11 @@ export namespace Bm {
export type OpenGoogleAuthDialog = { acctEmail?: string; scopes?: string[] };
export type OpenPage = { page: string; addUrlText?: string | UrlParams };
export type PassphraseEntry = { entered: boolean; initiatorFrameId?: string };
- export type ConfirmationResult = { confirm: boolean };
+ export type AsyncResult = { requestUid: string; payload: T };
+ export type ConfirmationResult = AsyncResult;
export type AuthWindowResult = { url?: string; error?: string };
export type Db = { f: string; args: unknown[] };
+ export type _tab_ = { contentScript?: boolean }; // eslint-disable-line @typescript-eslint/naming-convention
export type InMemoryStoreSet = {
acctEmail: string;
key: string;
@@ -78,12 +84,13 @@ export namespace Bm {
export type PgpMsgDecrypt = PgpMsgMethod.Arg.Decrypt;
export type PgpKeyBinaryToArmored = { binaryKeysData: Uint8Array };
export type Ajax = { req: JQuery.AjaxSettings; stack: string };
- export type AjaxProgress = { frameId: string; percent?: number; loaded: number; total: number; expectedTransferSize: number };
+ export type AjaxProgress = { operationId: string; percent?: number; loaded: number; total: number; expectedTransferSize: number };
export type AjaxGmailAttachmentGetChunk = { acctEmail: string; msgId: string; attachmentId: string };
export type ShowAttachmentPreview = { iframeUrl: string };
- export type ShowConfirmation = { text: string; isHTML: boolean; footer?: string };
+ export type ShowConfirmation = { text: string; isHTML: boolean; messageSender: string; requestUid: string; footer?: string };
export type ReRenderRecipient = { email: string };
- export type ShowConfirmationResult = { isConfirmed: boolean };
+ export type PgpBlockRetry = { frameId: string; messageSender: Dest };
+ export type PgpBlockReady = { frameId: string; messageSender: Dest };
export namespace Res {
export type GetActiveTabInfo = {
@@ -101,8 +108,7 @@ export namespace Bm {
export type PgpMsgDecrypt = DecryptResult;
export type PgpKeyBinaryToArmored = { keys: ArmoredKeyIdentityWithEmails[] };
export type AjaxGmailAttachmentGetChunk = { chunk: Buf };
- export type _tab_ = { tabId: string | null | undefined }; // eslint-disable-line @typescript-eslint/naming-convention
- export type ShowConfirmationResult = boolean;
+ export type _tab_ = { tabId: string | null | undefined; contentScript?: boolean }; // eslint-disable-line @typescript-eslint/naming-convention
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type Db = any; // not included in Any below
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -120,10 +126,12 @@ export namespace Bm {
| StoreGlobalGet
| StoreGlobalSet
| AjaxGmailAttachmentGetChunk
- | PgpKeyBinaryToArmored;
+ | PgpKeyBinaryToArmored
+ | ConfirmationResult;
}
export type AnyRequest =
+ | _tab_
| PassphraseEntry
| OpenPage
| OpenGoogleAuthDialog
@@ -154,11 +162,15 @@ export namespace Bm {
| StoreAcctSet
| PgpMsgDecrypt
| Ajax
+ | AjaxProgress
| ShowAttachmentPreview
| ShowConfirmation
| ReRenderRecipient
| PgpKeyBinaryToArmored
| AuthWindowResult
+ | RenderMessage
+ | PgpBlockReady
+ | PgpBlockRetry
| ConfirmationResult;
// export type RawResponselessHandler = (req: AnyRequest) => Promise;
@@ -195,6 +207,8 @@ export class TabIdRequiredError extends Error {}
export class BrowserMsg {
public static MAX_SIZE = 1024 * 1024; // 1MB
+ public static contentScriptsRegistry = new Set();
+
public static send = {
// todo - may want to organise this differently, seems to always confuse me when sending a message
bg: {
@@ -219,7 +233,6 @@ export class BrowserMsg {
BrowserMsg.sendAwait(undefined, 'pgpKeyBinaryToArmored', bm, true) as Promise,
},
},
- confirmationResult: (dest: Bm.Dest, bm: Bm.ConfirmationResult) => BrowserMsg.sendCatch(dest, 'confirmation_result', bm),
passphraseEntry: (dest: Bm.Dest, bm: Bm.PassphraseEntry) => BrowserMsg.sendCatch(dest, 'passphrase_entry', bm),
addEndSessionBtn: (dest: Bm.Dest) => BrowserMsg.sendCatch(dest, 'add_end_session_btn', {}),
openPage: (dest: Bm.Dest, bm: Bm.OpenPage) => BrowserMsg.sendCatch(dest, 'open_page', bm),
@@ -242,7 +255,7 @@ export class BrowserMsg {
notificationShow: (dest: Bm.Dest, bm: Bm.NotificationShow) => BrowserMsg.sendCatch(dest, 'notification_show', bm),
notificationShowAuthPopupNeeded: (dest: Bm.Dest, bm: Bm.NotificationShowAuthPopupNeeded) =>
BrowserMsg.sendCatch(dest, 'notification_show_auth_popup_needed', bm),
- showConfirmation: (dest: Bm.Dest, bm: Bm.ShowConfirmation) => BrowserMsg.sendCatch(dest, 'confirmation_show', bm),
+ showConfirmation: (bm: Bm.ShowConfirmation) => BrowserMsg.sendToParentWindow('confirmation_show', bm, 'confirmation_result'),
renderPublicKeys: (dest: Bm.Dest, bm: Bm.RenderPublicKeys) => BrowserMsg.sendCatch(dest, 'render_public_keys', bm),
replyPubkeyMismatch: (dest: Bm.Dest) => BrowserMsg.sendCatch(dest, 'reply_pubkey_mismatch', {}),
addPubkeyDialog: (dest: Bm.Dest, bm: Bm.AddPubkeyDialog) => BrowserMsg.sendCatch(dest, 'add_pubkey_dialog', bm),
@@ -252,8 +265,11 @@ export class BrowserMsg {
reRenderRecipient: (dest: Bm.Dest, bm: Bm.ReRenderRecipient) => BrowserMsg.sendCatch(dest, 'reRenderRecipient', bm),
showAttachmentPreview: (dest: Bm.Dest, bm: Bm.ShowAttachmentPreview) => BrowserMsg.sendCatch(dest, 'show_attachment_preview', bm),
ajaxProgress: (dest: Bm.Dest, bm: Bm.AjaxProgress) => BrowserMsg.sendCatch(dest, 'ajax_progress', bm),
- pgpBlockRender: (dest: Bm.Dest, bm: RenderMessageWithFrameId) => BrowserMsg.sendCatch(dest, 'pgp_block_render', bm),
+ pgpBlockRender: (dest: Bm.Dest, bm: RenderMessage) => BrowserMsg.sendCatch(dest, 'pgp_block_render', bm),
+ pgpBlockReady: (bm: Bm.PgpBlockReady) => BrowserMsg.sendToParentWindow('pgp_block_ready', bm),
+ pgpBlockRetry: (bm: Bm.PgpBlockRetry) => BrowserMsg.sendToParentWindow('pgp_block_retry', bm),
};
+ private static readonly processed: string[] = [];
/* eslint-disable @typescript-eslint/naming-convention */
private static HANDLERS_REGISTERED_BACKGROUND: Handlers = {};
private static HANDLERS_REGISTERED_FRAME: Handlers = {
@@ -289,9 +305,9 @@ export class BrowserMsg {
window.document.body.appendChild(div);
};
- public static tabId = async (): Promise => {
+ public static tabId = async (contentScript?: boolean): Promise => {
try {
- const { tabId } = (await BrowserMsg.sendAwait(undefined, '_tab_', undefined, true)) as Bm.Res._tab_;
+ const { tabId } = (await BrowserMsg.sendAwait(undefined, '_tab_', { contentScript }, true)) as Bm.Res._tab_;
return tabId;
} catch (e) {
if (e instanceof BgNotReadyErr) {
@@ -301,11 +317,11 @@ export class BrowserMsg {
}
};
- public static requiredTabId = async (attempts = 10, delay = 200): Promise => {
+ public static requiredTabId = async (contentScript?: boolean, attempts = 10, delay = 200): Promise => {
let tabId;
for (let i = 0; i < attempts; i++) {
// sometimes returns undefined right after browser start due to BgNotReadyErr
- tabId = await BrowserMsg.tabId();
+ tabId = await BrowserMsg.tabId(contentScript);
if (tabId) {
return tabId;
}
@@ -325,39 +341,28 @@ export class BrowserMsg {
BrowserMsg.HANDLERS_REGISTERED_FRAME[name] = handler;
};
- public static listen = (listenForTabId: string) => {
- const processed: string[] = [];
- chrome.runtime.onMessage.addListener((msg: Bm.Raw, sender, rawRespond: (rawResponse: Bm.RawResponse) => void) => {
- // console.debug(`listener(${listenForTabId}) new message: ${msg.name} to ${msg.to} with id ${msg.uid} from`, sender);
- try {
- if (msg.to === listenForTabId || msg.to === 'broadcast') {
- if (!processed.includes(msg.uid)) {
- processed.push(msg.uid);
- if (typeof BrowserMsg.HANDLERS_REGISTERED_FRAME[msg.name] !== 'undefined') {
- const handler: Bm.AsyncRespondingHandler = BrowserMsg.HANDLERS_REGISTERED_FRAME[msg.name];
- BrowserMsg.replaceObjUrlWithBuf(msg.data.bm, msg.data.objUrls)
- .then(bm => BrowserMsg.sendRawResponse(handler(bm, sender), rawRespond))
- .catch(e => BrowserMsg.sendRawResponse(Promise.reject(e), rawRespond));
- return true; // will respond
- } else if (msg.name !== '_tab_' && msg.to !== 'broadcast') {
- BrowserMsg.sendRawResponse(Promise.reject(new Error(`BrowserMsg.listen error: handler "${msg.name}" not set`)), rawRespond);
- return true; // will respond
- }
- } else {
- // sometimes received events get duplicated
- // while first event is being processed, second even will arrive
- // that's why we generate a unique id of each request (uid) and filter them above to identify truly unique requests
- // if we got here, that means we are handing a duplicate request
- // we'll indicate will respond = true, so that the processing of the actual request is not negatively affected
- // leaving it at "false" would respond with null, which would throw an error back to the original BrowserMsg sender:
- // "Error: BrowserMsg.sendAwait(pgpMsgDiagnosePubkeys) returned(null) with lastError: (no lastError)"
- // the duplication is likely caused by our routing mechanism. Sometimes browser will deliver the message directly as well as through bg
- return true;
+ // this is a means to get messages from child iframes
+ public static listenForWindowMessages = () => {
+ const allowedOrigin = Env.getExtensionOrigin();
+ window.addEventListener('message', e => {
+ if (e.origin === allowedOrigin) {
+ const msg = e.data as Bm.RawWindowMessageWithSender;
+ BrowserMsg.handleMsg(msg, {}, (rawResponse: Bm.RawResponse) => {
+ if (msg.responseName) {
+ // send response as a new request
+ BrowserMsg.sendRaw(msg.data.bm.messageSender, msg.responseName, rawResponse.result as Dict, rawResponse.objUrls).catch(Catch.reportErr);
}
- }
- } catch (e) {
- BrowserMsg.sendRawResponse(Promise.reject(e), rawRespond);
- return true; // will respond
+ });
+ }
+ });
+ };
+
+ public static listen = (dest: string) => {
+ chrome.runtime.onMessage.addListener((msg: Bm.Raw, sender, rawRespond: (rawResponse: Bm.RawResponse) => void) => {
+ // console.debug(`listener(${dest}) new message: ${msg.name} to ${msg.to} with id ${msg.uid} from`, sender);
+ if (msg.to === dest || msg.to === 'broadcast') {
+ BrowserMsg.handleMsg(msg, sender, rawRespond);
+ return true;
}
return false; // will not respond
});
@@ -425,6 +430,47 @@ export class BrowserMsg {
});
};
+ private static sendToParentWindow = (name: string, bm: Dict & { messageSender: Bm.Dest }, responseName?: string) => {
+ const raw: Bm.RawWindowMessageWithSender = {
+ data: { bm, objUrls: {} },
+ name,
+ stack: '',
+ // eslint-disable-next-line no-null/no-null
+ to: null,
+ uid: Str.sloppyRandom(10),
+ responseName,
+ };
+ window.parent.postMessage(raw, '*');
+ };
+
+ private static handleMsg = (msg: Bm.Raw, sender: chrome.runtime.MessageSender, rawRespond: (rawResponse: Bm.RawResponse) => void) => {
+ try {
+ if (!BrowserMsg.processed.includes(msg.uid)) {
+ // todo: Set or ExpirationCache?
+ BrowserMsg.processed.push(msg.uid);
+ if (typeof BrowserMsg.HANDLERS_REGISTERED_FRAME[msg.name] !== 'undefined') {
+ const handler: Bm.AsyncRespondingHandler = BrowserMsg.HANDLERS_REGISTERED_FRAME[msg.name];
+ BrowserMsg.replaceObjUrlWithBuf(msg.data.bm, msg.data.objUrls)
+ .then(bm => BrowserMsg.sendRawResponse(handler(bm, sender), rawRespond))
+ .catch(e => BrowserMsg.sendRawResponse(Promise.reject(e), rawRespond));
+ } else if (msg.name !== '_tab_' && msg.to !== 'broadcast') {
+ BrowserMsg.sendRawResponse(Promise.reject(new Error(`BrowserMsg.listen error: handler "${msg.name}" not set`)), rawRespond);
+ }
+ } else {
+ // sometimes received events get duplicated
+ // while first event is being processed, second even will arrive
+ // that's why we generate a unique id of each request (uid) and filter them above to identify truly unique requests
+ // if we got here, that means we are handing a duplicate request
+ // we'll indicate will respond = true, so that the processing of the actual request is not negatively affected
+ // leaving it at "false" would respond with null, which would throw an error back to the original BrowserMsg sender:
+ // "Error: BrowserMsg.sendAwait(pgpMsgDiagnosePubkeys) returned(null) with lastError: (no lastError)"
+ // the duplication is likely caused by our routing mechanism. Sometimes browser will deliver the message directly as well as through bg
+ }
+ } catch (e) {
+ BrowserMsg.sendRawResponse(Promise.reject(e), rawRespond);
+ }
+ };
+
/**
* When sending message from iframe within extension page, the browser will deliver the message to BOTH
* the parent frame as well as the background (when we ment to just send to parent).
@@ -439,8 +485,7 @@ export class BrowserMsg {
if (Catch.browser().name !== 'chrome') {
return true; // only chrome sends messages directly to extension frame parent (in addition to sending to bg)
}
- if (destination !== `${sender.tab.id}:0`) {
- // zero mains the main frame in a tab, the parent frame
+ if (BrowserMsg.contentScriptsRegistry.has(destination)) {
return true; // not sending to a parent (must relay, browser does not send directly)
}
if (sender.url?.includes(chrome.runtime.id) && sender.tab.url?.startsWith('https://')) {
@@ -462,12 +507,29 @@ export class BrowserMsg {
const handler: Bm.AsyncRespondingHandler = BrowserMsg.HANDLERS_REGISTERED_BACKGROUND[name];
return await handler(bm, 'background');
}
- return await new Promise((resolve, reject) => {
+ return await BrowserMsg.sendRaw(
+ destString,
+ name,
+ bm,
// here browser messaging is used - msg has to be serializable - Buf instances need to be converted to object urls, and back upon receipt
- const objUrls = BrowserMsg.replaceBufWithObjUrlInplace(bm);
+ BrowserMsg.replaceBufWithObjUrlInplace(bm),
+ awaitRes,
+ isBackgroundPage
+ );
+ };
+
+ private static sendRaw = async (
+ destString: string | undefined,
+ name: string,
+ bm: Dict,
+ objUrls: Dict,
+ awaitRes = false,
+ isBackgroundPage = false
+ ): Promise => {
+ return await new Promise((resolve, reject) => {
const msg: Bm.Raw = {
name,
- data: { bm: bm!, objUrls }, // eslint-disable-line @typescript-eslint/no-non-null-assertion
+ data: { bm, objUrls },
to: destString || null, // eslint-disable-line no-null/no-null
uid: Str.sloppyRandom(10),
stack: Catch.stackTrace(),
diff --git a/extension/js/common/browser/ui.ts b/extension/js/common/browser/ui.ts
index 9af856c6830..a87f8369933 100644
--- a/extension/js/common/browser/ui.ts
+++ b/extension/js/common/browser/ui.ts
@@ -4,7 +4,7 @@
import { ApiErr } from '../api/shared/api-error.js';
import { Catch } from '../platform/catch.js';
-import { Dict, Url } from '../core/common.js';
+import { Dict, Str, Url } from '../core/common.js';
import Swal, { SweetAlertIcon, SweetAlertPosition, SweetAlertResult } from 'sweetalert2';
import { Xss } from '../platform/xss.js';
import { Bm, BrowserMsg } from './browser-msg.js';
@@ -13,20 +13,38 @@ type NamedSels = Dict>;
type ProvidedEventHandler = (e: HTMLElement, event: JQuery.TriggeredEvent) => void | Promise;
-export interface ConfirmationResultTracker {
- getParentTabId: () => string;
- confirmationResultResolver?: (confirm: boolean) => void;
+export interface BrowserMsgResponseTracker {
+ getDest: () => string;
}
+export type ConfirmationResultTracker = BrowserMsgResponseTracker;
+
export class CommonHandlers {
- public static createConfirmationResultHandler: (view: ConfirmationResultTracker) => Bm.AsyncResponselessHandler = view => {
- return async ({ confirm }: Bm.ConfirmationResult) => {
- view.confirmationResultResolver?.(confirm);
+ protected static respondMap = new Map void>();
+
+ public static createAsyncResultHandler = () => {
+ return async ({ payload, requestUid }: Bm.AsyncResult) => {
+ const respond = CommonHandlers.respondMap.get(requestUid);
+ if (respond) {
+ respond(payload);
+ CommonHandlers.respondMap.delete(requestUid);
+ }
};
};
- public static showConfirmationHandler: Bm.AsyncResponselessHandler = async ({ text, isHTML, footer }: Bm.ShowConfirmation) => {
- const confirm = await Ui.modal.confirm(text, isHTML, footer);
- BrowserMsg.send.confirmationResult('broadcast', { confirm });
+
+ public static sendRequestAndHandleAsyncResult = async (send: (requestUid: string) => void): Promise => {
+ const requestUid = Str.sloppyRandom(10);
+ const p = new Promise((resolve: (value: T) => void) => {
+ CommonHandlers.respondMap.set(requestUid, resolve);
+ });
+ send(requestUid);
+ return await p;
+ };
+
+ // for specific types
+ public static showConfirmationHandler: Bm.AsyncRespondingHandler = async ({ text, isHTML, footer, requestUid }: Bm.ShowConfirmation) => {
+ const payload = await Ui.modal.confirm(text, isHTML, footer);
+ return { requestUid, payload };
};
}
@@ -331,12 +349,16 @@ export class Ui {
* Presents a modal where user can respond with confirm or cancel.
* Awaiting this will give you the users choice as a boolean.
*/
- confirm: async (text: string, isHTML = false, footer?: string): Promise => {
- const p = new Promise((resolve: (value: boolean) => void) => {
- confirmationResultTracker.confirmationResultResolver = resolve;
+ confirm: (text: string, isHTML = false, footer?: string): Promise => {
+ return CommonHandlers.sendRequestAndHandleAsyncResult(requestUid => {
+ BrowserMsg.send.showConfirmation({
+ text,
+ isHTML,
+ footer,
+ messageSender: confirmationResultTracker.getDest(),
+ requestUid,
+ });
});
- BrowserMsg.send.showConfirmation(confirmationResultTracker.getParentTabId(), { text, isHTML, footer });
- return await p;
},
};
};
diff --git a/extension/js/common/message-renderer.ts b/extension/js/common/message-renderer.ts
index b5af3a0cdc0..f95fffe3283 100644
--- a/extension/js/common/message-renderer.ts
+++ b/extension/js/common/message-renderer.ts
@@ -320,7 +320,7 @@ export class MessageRenderer {
// we could change 'Getting file info..' to 'Loading signed message..' in attachment_loader element
const raw = await this.downloader.msgGetRaw(msgId);
loaderContext.hideAttachment(attachmentSel);
- await this.setMsgBodyAndStartProcessing(loaderContext, 'signedDetached', messageInfo.printMailInfo, messageInfo.from?.email, renderModule =>
+ this.setMsgBodyAndStartProcessing(loaderContext, 'signedDetached', messageInfo.printMailInfo, messageInfo.from?.email, renderModule =>
this.processMessageWithDetachedSignatureFromRaw(raw, renderModule, messageInfo.from?.email, body)
);
return 'hidden'; // native attachment should be hidden, the "attachment" goes to the message container
@@ -349,8 +349,8 @@ export class MessageRenderer {
loaderContext.prependEncryptedAttachment(a);
return 'replaced'; // native should be hidden, custom should appear instead
} else if (treatAs === 'encryptedMsg') {
- await this.setMsgBodyAndStartProcessing(loaderContext, treatAs, messageInfo.printMailInfo, messageInfo.from?.email, (renderModule, frameId) =>
- this.processCryptoMessage(a, renderModule, frameId, messageInfo.from?.email, messageInfo.isPwdMsgBasedOnMsgSnippet, messageInfo.plainSubject)
+ this.setMsgBodyAndStartProcessing(loaderContext, treatAs, messageInfo.printMailInfo, messageInfo.from?.email, renderModule =>
+ this.processCryptoMessage(a, renderModule, messageInfo.from?.email, messageInfo.isPwdMsgBasedOnMsgSnippet, messageInfo.plainSubject)
);
return 'hidden'; // native attachment should be hidden, the "attachment" goes to the message container
} else if (treatAs === 'privateKey') {
@@ -372,14 +372,12 @@ export class MessageRenderer {
}
};
- public startProcessingInlineBlocks = async (relayManager: RelayManager, factory: XssSafeFactory, messageInfo: MessageInfo, blocks: Dict) => {
- await Promise.all(
- Object.entries(blocks).map(([frameId, block]) =>
- this.relayAndStartProcessing(relayManager, factory, frameId, messageInfo.printMailInfo, messageInfo.from?.email, renderModule =>
- this.renderMsgBlock(block, renderModule, messageInfo.from?.email, messageInfo.isPwdMsgBasedOnMsgSnippet, messageInfo.plainSubject)
- )
- )
- );
+ public startProcessingInlineBlocks = (relayManager: RelayManager, factory: XssSafeFactory, messageInfo: MessageInfo, blocks: Dict) => {
+ for (const [frameId, block] of Object.entries(blocks)) {
+ this.relayAndStartProcessing(relayManager, factory, frameId, messageInfo.printMailInfo, messageInfo.from?.email, renderModule =>
+ this.renderMsgBlock(block, renderModule, messageInfo.from?.email, messageInfo.isPwdMsgBasedOnMsgSnippet, messageInfo.plainSubject)
+ );
+ }
};
public deleteExpired = (): void => {
@@ -577,48 +575,41 @@ export class MessageRenderer {
return isEncrypted ? { publicKeys } : {};
};
- private relayAndStartProcessing = async (
+ private relayAndStartProcessing = (
relayManager: RelayManager,
factory: XssSafeFactory,
frameId: string,
printMailInfo: PrintMailInfo | undefined,
senderEmail: string | undefined,
- cb: (renderModule: RenderInterface, frameId: string) => Promise<{ publicKeys?: string[]; needPassphrase?: string[] }>
- ): Promise<{ processor: Promise }> => {
- const renderModule = relayManager.createRelay(frameId);
- if (printMailInfo) {
- renderModule.setPrintMailInfo(printMailInfo);
- }
- const processor = cb(renderModule, frameId)
- .then(async result => {
- const appendAfter = $(`iframe#${frameId}`); // todo: review inbox-active-thread -- may fail
- // todo: how publicKeys and needPassphrase interact?
- for (const armoredPubkey of result.publicKeys ?? []) {
- appendAfter.after(factory.embeddedPubkey(armoredPubkey, this.isOutgoing(senderEmail)));
- }
- while (result.needPassphrase && !renderModule.cancellation.cancel) {
- // if we need passphrase, we have to be able to re-try decryption indefinitely on button presses,
- // so we can only release resources when the frame is detached
- await PassphraseStore.waitUntilPassphraseChanged(this.acctEmail, result.needPassphrase, 1000, renderModule.cancellation);
- if (renderModule.cancellation.cancel) {
- if (this.debug) {
- console.debug('Destination frame was detached -- stopping processing');
- }
- return;
+ cb: (renderModule: RenderInterface) => Promise<{ publicKeys?: string[]; needPassphrase?: string[] }>
+ ): void => {
+ relayManager.createAndStartRelay(frameId, async (renderModule: RenderInterface) => {
+ if (printMailInfo) {
+ renderModule.setPrintMailInfo(printMailInfo);
+ }
+ let result = await cb(renderModule);
+ // todo: review inbox-active-thread -- may fail if the frameId isn't in the DOM yet
+ // we may stop here and wait until renderModule is attached (receive a signal from RelayManager based on frameData)
+ const appendAfter = $(`iframe#${frameId}`);
+ // todo: how publicKeys and needPassphrase interact?
+ for (const armoredPubkey of result.publicKeys ?? []) {
+ appendAfter.after(factory.embeddedPubkey(armoredPubkey, this.isOutgoing(senderEmail)));
+ }
+ while (result.needPassphrase && !renderModule.cancellation.cancel) {
+ // wait for either passphrase or cancellation
+ await PassphraseStore.waitUntilPassphraseChanged(this.acctEmail, result.needPassphrase, 1000, renderModule.cancellation);
+ if (renderModule.cancellation.cancel) {
+ if (this.debug) {
+ console.debug('Destination frame was detached -- stopping processing');
}
- renderModule.clearErrorStatus();
- renderModule.renderText('Decrypting...');
- result = await cb(renderModule, frameId);
- // I guess, no additional publicKeys will appear here for display...
+ return;
}
- })
- .catch(e => {
- // normally no exceptions come to this point so let's report it
- Catch.reportErr(e);
- renderModule.renderErr(Xss.escape(String(e)), undefined);
- })
- .finally(() => relayManager.done(frameId));
- return { processor };
+ renderModule.clearErrorStatus();
+ renderModule.renderText('Decrypting...');
+ result = await cb(renderModule);
+ // I guess, no additional publicKeys will appear here for display...
+ }
+ });
};
private renderMsgBlock = async (
@@ -758,22 +749,21 @@ export class MessageRenderer {
return {};
};
- private setMsgBodyAndStartProcessing = async (
+ private setMsgBodyAndStartProcessing = (
loaderContext: LoaderContextInterface,
type: string, // for diagnostics
printMailInfo: PrintMailInfo | undefined,
senderEmail: string | undefined,
- cb: (renderModule: RenderInterface, frameId: string) => Promise<{ publicKeys?: string[] }>
- ): Promise<{ processor: Promise }> => {
+ cb: (renderModule: RenderInterface) => Promise<{ publicKeys?: string[] }>
+ ) => {
const { frameId, frameXssSafe } = this.factory.embeddedMsg(type); // xss-safe-factory
loaderContext.setMsgBody_DANGEROUSLY(frameXssSafe, 'set'); // xss-safe-value
- return await this.relayAndStartProcessing(this.relayManager, this.factory, frameId, printMailInfo, senderEmail, cb);
+ this.relayAndStartProcessing(this.relayManager, this.factory, frameId, printMailInfo, senderEmail, cb);
};
private processCryptoMessage = async (
attachment: Attachment,
renderModule: RenderInterface,
- frameId: string,
senderEmail: string | undefined,
isPwdMsgBasedOnMsgSnippet: boolean | undefined,
plainSubject: string | undefined
@@ -782,14 +772,8 @@ export class MessageRenderer {
if (!attachment.hasData()) {
// todo: implement cache similar to chunk downloads
// note: this cache should return void or throw an exception because the data bytes are set to the Attachment object
- this.relayManager.renderProgressText(frameId, 'Retrieving message...');
- await this.gmail.fetchAttachment(attachment, expectedTransferSize => {
- return {
- frameId,
- expectedTransferSize,
- download: (percent, loaded, total) => this.relayManager.renderProgress({ frameId, percent, loaded, total, expectedTransferSize }), // shortcut
- };
- });
+ const progressFunction = renderModule.startProgressRendering('Retrieving message...');
+ await this.gmail.fetchAttachment(attachment, progressFunction);
}
const armoredMsg = PgpArmor.clip(attachment.getData().toUtfStr());
if (!armoredMsg) {
diff --git a/extension/js/common/relay-manager.ts b/extension/js/common/relay-manager.ts
index 6ef4289344e..000eec5c912 100644
--- a/extension/js/common/relay-manager.ts
+++ b/extension/js/common/relay-manager.ts
@@ -9,22 +9,26 @@ import { RenderMessage } from './render-message.js';
import { RenderRelay } from './render-relay.js';
type FrameEntry = {
- readyToReceive?: true;
+ tabId?: string;
queue: RenderMessage[];
- progressText?: string;
relay?: RenderRelay;
};
export class RelayManager implements RelayManagerInterface {
- private static readonly completionMessage: RenderMessage = { done: true };
private readonly frames = new Map();
- public constructor(private tabId: string, private debug: boolean = false) {
+ public constructor(private debug: boolean = false) {
const framesObserver = new MutationObserver(async mutationsList => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
- for (const removedNode of mutation.removedNodes) {
- this.dropRemovedNodes(removedNode);
+ const removedFrameIds = this.findFrameIds(mutation.removedNodes);
+ const addedFrameIds = this.findFrameIds(mutation.addedNodes);
+ for (const frameId of removedFrameIds) {
+ if (addedFrameIds.includes(frameId)) {
+ this.restartRelay(frameId);
+ } else {
+ this.dropRemovedFrame(frameId);
+ }
}
}
}
@@ -32,116 +36,99 @@ export class RelayManager implements RelayManagerInterface {
framesObserver.observe(window.document, { subtree: true, childList: true });
}
- public static getPercentage = (percent: number | undefined, loaded: number, total: number, expectedTransferSize: number) => {
- if (typeof percent === 'undefined') {
- if (total || expectedTransferSize) {
- percent = Math.round((loaded / (total || expectedTransferSize)) * 100);
- }
- }
- return percent;
- };
-
- public relay = (frameId: string, message: RenderMessage) => {
+ public relay = (frameId: string, message: RenderMessage, dontEnqueue?: boolean) => {
const frameData = this.frames.get(frameId);
if (frameData) {
- frameData.queue.push(message);
- if (frameData.readyToReceive) {
- this.flush({ frameId, queue: frameData.queue });
+ if (!dontEnqueue || this.flushIfReady(frameId)) {
+ frameData.queue.push(message);
+ this.flushIfReady(frameId);
}
}
};
- public createRelay = (frameId: string): RenderInterface => {
+ public createAndStartRelay = (frameId: string, processor: (renderModule: RenderInterface) => Promise) => {
const frameData = this.getOrCreate(frameId);
- const relay = new RenderRelay(this, frameId);
+ const relay = new RenderRelay(this, frameId, processor);
frameData.relay = relay;
- return relay;
+ relay.start();
};
- public done = (frameId: string) => {
- this.relay(frameId, RelayManager.completionMessage);
+ public retry = (frameId: string, messageSender: Bm.Dest) => {
+ const frameData = this.frames.get(frameId);
+ if (frameData?.tabId === messageSender) {
+ frameData.relay?.executeRetry();
+ }
};
- public handleMessageFromFrame = (data: unknown) => {
- const typedData = data as { readyToReceive?: string; retry?: string } | undefined;
- if (typeof typedData?.readyToReceive === 'string') {
- this.readyToReceive(typedData.readyToReceive);
- }
- if (typeof typedData?.retry === 'string') {
- this.retry(typedData.retry);
+ public renderProgress = (r: Bm.AjaxProgress) => {
+ // simply forward this message to all relays
+ // the correct recipient will recognize itself by operationId match
+ // and return true
+ for (const [, value] of this.frames) {
+ if (value.relay?.renderProgress(r)) break;
}
};
- public renderProgressText = (frameId: string, text: string) => {
+ public associate = (frameId: string, tabId: string) => {
+ const frameData = this.getOrCreate(frameId);
+ frameData.tabId = tabId;
+ this.flushIfReady(frameId);
+ };
+
+ public restartRelay = (frameId: string) => {
const frameData = this.frames.get(frameId);
- if (frameData) {
- frameData.progressText = text;
- this.relay(frameId, { renderText: text });
+ if (frameData?.relay) {
+ frameData.relay.cancellation.cancel = true; // cancel the old processing to prevent interference and release resources
+ const relay = frameData.relay.clone();
+ this.frames.set(frameId, { queue: [], relay }); // wire the new relay
+ relay.start(); // start the processor anew
}
};
- public renderProgress = ({ frameId, percent, loaded, total, expectedTransferSize }: Bm.AjaxProgress) => {
- const perc = RelayManager.getPercentage(percent, loaded, total, expectedTransferSize);
- if (typeof perc !== 'undefined') {
- const frameData = this.frames.get(frameId);
- if (frameData?.readyToReceive && typeof frameData.progressText !== 'undefined') {
- this.relay(frameId, { renderText: `${frameData.progressText} ${perc}%` });
+ private findFrameIds = (nodes: NodeList): string[] => {
+ const frameIds: string[] = [];
+ for (const node of nodes) {
+ if (node.nodeType === Node.ELEMENT_NODE) {
+ const element = node as HTMLElement;
+ if (element.tagName === 'IFRAME') {
+ frameIds.push(element.id);
+ continue;
+ }
}
+ frameIds.push(...this.findFrameIds(node.childNodes));
}
+ return frameIds;
};
- private dropRemovedNodes = (removedNode: Node) => {
- let frameId: string | undefined;
- if (removedNode.nodeType === Node.ELEMENT_NODE) {
- const element = removedNode as HTMLElement;
- if (element.tagName === 'IFRAME') {
- frameId = element.id;
- }
+ private dropRemovedFrame = (frameId: string) => {
+ if (this.debug) {
+ console.debug('releasing resources connected to frameId=', frameId);
}
- if (frameId) {
- if (this.debug) {
- console.debug('releasing resources connected to frameId=', frameId);
- }
- const frameData = this.frames.get(frameId);
- if (frameData) {
- if (frameData.relay?.cancellation) frameData.relay.cancellation.cancel = true;
- this.frames.delete(frameId);
- }
- } else {
- for (const childNode of removedNode.childNodes) {
- this.dropRemovedNodes(childNode);
- }
+ const frameData = this.frames.get(frameId);
+ if (frameData) {
+ if (frameData.relay?.cancellation) frameData.relay.cancellation.cancel = true;
+ this.frames.delete(frameId);
}
};
private getOrCreate = (frameId: string): FrameEntry => {
const frameEntry = this.frames.get(frameId);
if (frameEntry) return frameEntry;
- const newFrameEntry = { queue: [], cancellation: { cancel: false } };
+ const newFrameEntry = { queue: [] };
this.frames.set(frameId, newFrameEntry);
return newFrameEntry;
};
- private flush = ({ frameId, queue }: { frameId: string; queue: RenderMessage[] }) => {
- while (true) {
- const message = queue.shift();
+ private flushIfReady = (frameId: string) => {
+ const frameData = this.frames.get(frameId);
+ while (frameData?.tabId) {
+ const message = frameData.queue.shift();
if (message) {
- BrowserMsg.send.pgpBlockRender(this.tabId, { ...message, frameId });
- if (message === RelayManager.completionMessage) {
- this.frames.delete(frameId);
- }
- } else break;
+ BrowserMsg.send.pgpBlockRender(frameData.tabId, message);
+ } else {
+ return true; // flushed
+ }
}
- };
-
- private readyToReceive = (frameId: string) => {
- const frameData = this.getOrCreate(frameId);
- frameData.readyToReceive = true;
- this.flush({ frameId, queue: frameData.queue });
- };
-
- private retry = (frameId: string) => {
- const frameData = this.frames.get(frameId);
- frameData?.relay?.executeRetry();
+ return false; // not flushed
};
}
diff --git a/extension/js/common/render-interface.ts b/extension/js/common/render-interface.ts
index 01d6373b628..07d85c566f1 100644
--- a/extension/js/common/render-interface.ts
+++ b/extension/js/common/render-interface.ts
@@ -2,6 +2,7 @@
'use strict';
+import { ProgressCb, ProgressDestFrame } from './api/shared/api.js';
import { TransferableAttachment } from './core/attachment.js';
import { PromiseCancellation } from './core/common.js';
import { PrintMailInfo } from './render-message.js';
@@ -16,6 +17,7 @@ export interface RenderInterfaceBase {
export interface RenderInterface extends RenderInterfaceBase {
cancellation: PromiseCancellation;
+ startProgressRendering(text: string): (expectedTransferSize: number) => { download: ProgressCb } | ProgressDestFrame;
renderAsRegularContent(content: string): void;
setPrintMailInfo(info: PrintMailInfo): void;
clearErrorStatus(): void;
diff --git a/extension/js/common/render-message.ts b/extension/js/common/render-message.ts
index 8fee3bd6673..160e6cbfcf9 100644
--- a/extension/js/common/render-message.ts
+++ b/extension/js/common/render-message.ts
@@ -40,7 +40,3 @@ export type RenderMessage = {
printMailInfo?: PrintMailInfo;
renderAsRegularContent?: string;
};
-
-export type RenderMessageWithFrameId = {
- frameId: string;
-} & RenderMessage;
diff --git a/extension/js/common/render-relay.ts b/extension/js/common/render-relay.ts
index a03481ae02b..6fac53b761e 100644
--- a/extension/js/common/render-relay.ts
+++ b/extension/js/common/render-relay.ts
@@ -6,12 +6,74 @@ import { RelayManagerInterface } from './relay-manager-interface.js';
import { RenderInterface } from './render-interface.js';
import { PrintMailInfo, RenderMessage } from './render-message.js';
import { TransferableAttachment } from './core/attachment.js';
-import { PromiseCancellation } from './core/common.js';
+import { PromiseCancellation, Str } from './core/common.js';
+import { Catch } from './platform/catch.js';
+import { Xss } from './platform/xss.js';
+import { ProgressCb } from './api/shared/api.js';
+import { Bm } from './browser/browser-msg.js';
export class RenderRelay implements RenderInterface {
public readonly cancellation: PromiseCancellation = { cancel: false };
private retry?: () => void;
- public constructor(private relayManager: RelayManagerInterface, private frameId: string) {}
+ private progressOperation?: {
+ text: string;
+ operationId: string; // we can possibly receive a callback from an operation started by the replaced RenderRelay, so need to check operationId
+ };
+ public constructor(
+ private relayManager: RelayManagerInterface,
+ private frameId: string,
+ private processor: (renderModule: RenderInterface) => Promise
+ ) {}
+
+ public static getPercentage = (percent: number | undefined, loaded: number, total: number, expectedTransferSize: number) => {
+ if (typeof percent === 'undefined') {
+ if (total || expectedTransferSize) {
+ percent = Math.round((loaded / (total || expectedTransferSize)) * 100);
+ }
+ }
+ return percent;
+ };
+
+ public startProgressRendering = (text: string) => {
+ this.relay({ renderText: text }); // we want to enqueue this initial message in case of hanging...
+ const operationId = Str.sloppyRandom(10);
+ this.progressOperation = { text, operationId };
+ return (expectedTransferSize: number) => {
+ // the `download` shortcut function can be used in some cases
+ // if not lost by messaging, it will be given priority over message-based progress implementation
+ const download: ProgressCb = (percent, loaded, total) => this.renderProgress({ operationId, percent, loaded, total, expectedTransferSize });
+ return {
+ operationId,
+ expectedTransferSize,
+ download, // shortcut
+ };
+ };
+ };
+
+ public renderProgress = ({ operationId, percent, loaded, total, expectedTransferSize }: Bm.AjaxProgress) => {
+ if (this.progressOperation && this.progressOperation.operationId === operationId) {
+ const perc = RenderRelay.getPercentage(percent, loaded, total, expectedTransferSize);
+ if (typeof perc !== 'undefined') {
+ this.relay({ renderText: `${this.progressOperation.text} ${perc}%` }, { progressOperationRendering: true });
+ }
+ return true;
+ }
+ return false;
+ };
+
+ public clone = () => {
+ return new RenderRelay(this.relayManager, this.frameId, this.processor);
+ };
+
+ public start = () => {
+ this.processor(this)
+ .catch(e => {
+ // normally no exceptions come to this point so let's report it
+ Catch.reportErr(e);
+ this.renderErr(Xss.escape(String(e)), undefined);
+ })
+ .finally(() => this.relay({ done: true }));
+ };
public renderErr = (errBoxContent: string, renderRawMsg: string | undefined, errMsg?: string | undefined) => {
this.relay({ renderErr: { errBoxContent, renderRawMsg, errMsg } });
@@ -78,7 +140,13 @@ export class RenderRelay implements RenderInterface {
}
};
- private relay = (message: RenderMessage) => {
- this.relayManager.relay(this.frameId, message);
+ private relay = (message: RenderMessage, options?: { progressOperationRendering: true }) => {
+ if (!this.cancellation.cancel) {
+ if (!options?.progressOperationRendering) {
+ // "unsubscribe" from further progress callbacks
+ this.progressOperation = undefined;
+ }
+ this.relayManager.relay(this.frameId, message, options?.progressOperationRendering);
+ }
};
}
diff --git a/extension/js/content_scripts/webmail/gmail-element-replacer.ts b/extension/js/content_scripts/webmail/gmail-element-replacer.ts
index 744cb7929eb..b7d424c8ed9 100644
--- a/extension/js/content_scripts/webmail/gmail-element-replacer.ts
+++ b/extension/js/content_scripts/webmail/gmail-element-replacer.ts
@@ -93,7 +93,9 @@ export class GmailElementReplacer implements WebmailElementReplacer {
public reinsertReplyBox = (replyMsgId: string) => {
const params: FactoryReplyParams = { replyMsgId };
- $('.reply_message_iframe_container:visible').last().append(this.factory.embeddedReply(params, false, true)); // xss-safe-value
+ $('.reply_message_iframe_container:visible')
+ .last()
+ .append(this.factory.embeddedReply(params, false, true)); // xss-safe-value
};
public scrollToReplyBox = (replyMsgId: string) => {
@@ -192,7 +194,7 @@ export class GmailElementReplacer implements WebmailElementReplacer {
if (this.debug) {
console.debug('replaceArmoredBlocks() for of emailsContainingPgpBlock -> emailContainer replaced');
}
- await this.messageRenderer.startProcessingInlineBlocks(this.relayManager, this.factory, setMessageInfo, blocksInFrames).catch(Catch.reportErr);
+ this.messageRenderer.startProcessingInlineBlocks(this.relayManager, this.factory, setMessageInfo, blocksInFrames);
}
};
diff --git a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts b/extension/js/content_scripts/webmail/setup-webmail-content-script.ts
index 5f5ce571a51..f220e831448 100644
--- a/extension/js/content_scripts/webmail/setup-webmail-content-script.ts
+++ b/extension/js/content_scripts/webmail/setup-webmail-content-script.ts
@@ -104,7 +104,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
};
const initInternalVars = async (acctEmail: string) => {
- const tabId = await BrowserMsg.requiredTabId(30, 1000); // keep trying for 30 seconds
+ const tabId = await BrowserMsg.requiredTabId(true, 30, 1000); // keep trying for 30 seconds
const notifications = new Notifications();
const factory = new XssSafeFactory(acctEmail, tabId, win.reloadable_class, win.destroyable_class);
const inject = new Injector(webmailSpecific.name, webmailSpecific.variant, factory);
@@ -219,6 +219,12 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
BrowserMsg.addListener('add_pubkey_dialog', async ({ emails }: Bm.AddPubkeyDialog) => {
await factory.showAddPubkeyDialog(emails);
});
+ BrowserMsg.addListener('pgp_block_ready', async ({ frameId, messageSender }: Bm.PgpBlockReady) => {
+ relayManager.associate(frameId, messageSender);
+ });
+ BrowserMsg.addListener('pgp_block_retry', async ({ frameId, messageSender }: Bm.PgpBlockRetry) => {
+ relayManager.retry(frameId, messageSender);
+ });
BrowserMsg.addListener('notification_show', async ({ notification, callbacks, group }: Bm.NotificationShow) => {
notifications.show(notification, callbacks, group);
$('body').one(
@@ -238,6 +244,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
relayManager.renderProgress(progress);
});
BrowserMsg.listen(tabId);
+ BrowserMsg.listenForWindowMessages(); // listen for direct messages from child iframes
};
const saveAcctEmailFullNameIfNeeded = async (acctEmail: string) => {
@@ -420,7 +427,7 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
await showNotificationsAndWaitTilAcctSetUp(acctEmail, notifications);
Catch.setHandledTimeout(() => updateClientConfiguration(acctEmail), 0);
const ppEvent: { entered?: boolean } = {};
- const relayManager = new RelayManager(tabId);
+ const relayManager = new RelayManager();
browserMsgListen(acctEmail, tabId, inject, factory, notifications, relayManager, ppEvent);
const clientConfiguration = await ClientConfiguration.newInstance(acctEmail);
await startPullingKeysFromEkm(
@@ -430,11 +437,6 @@ export const contentScriptSetupIfVacant = async (webmailSpecific: WebmailSpecifi
ppEvent,
Catch.try(() => notifyExpiringKeys(acctEmail, clientConfiguration, notifications))
);
- window.addEventListener('message', e => {
- if (e.origin === Env.getExtensionOrigin()) {
- relayManager.handleMessageFromFrame(e.data);
- }
- });
await webmailSpecific.start(acctEmail, clientConfiguration, inject, notifications, factory, notifyMurdered, relayManager);
} catch (e) {
if (e instanceof TabIdRequiredError) {
diff --git a/test/source/browser/controllable.ts b/test/source/browser/controllable.ts
index e575de2ac7d..85c0b532c33 100644
--- a/test/source/browser/controllable.ts
+++ b/test/source/browser/controllable.ts
@@ -503,7 +503,7 @@ abstract class ControllableBase {
if (matchingFrames.length > 1) {
throw Error(`More than one frame found: ${urlMatchables.join(',')}`);
} else if (matchingFrames.length === 1) {
- return new ControllableFrame(matchingFrames[0]);
+ return new ControllableFrame(matchingFrames[0], this.getPage());
}
await Util.sleep(1);
}
@@ -518,7 +518,7 @@ abstract class ControllableBase {
const resolvePromise: Promise = (async () => {
const downloadPath = path.resolve(__dirname, 'download', Util.lousyRandom());
mkdirp.sync(downloadPath);
- const page = 'page' in this.target ? this.target.page() : this.target;
+ const page = this.getPage().target;
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-underscore-dangle
await (page as any)._client().send('Page.setDownloadBehavior', { behavior: 'allow', downloadPath });
if (typeof selector === 'string') {
@@ -669,6 +669,8 @@ abstract class ControllableBase {
await Util.sleep(0.2);
}
};
+
+ public abstract getPage(): ControllablePage;
}
export class ControllableAlert {
@@ -692,16 +694,23 @@ export class ControllableAlert {
class ConsoleEvent {
// eslint-disable-next-line no-empty-function
- public constructor(public type: string, public text: string) {}
+ public constructor(
+ public type: string,
+ public text: string
+ ) {}
}
export class ControllablePage extends ControllableBase {
+ public target: Page;
public consoleMsgs: (ConsoleMessage | ConsoleEvent)[] = [];
public alerts: ControllableAlert[] = [];
private preventclose = false;
private acceptUnloadAlert = false;
- public constructor(public t: AvaContext, public page: Page) {
+ public constructor(
+ public t: AvaContext,
+ public page: Page
+ ) {
super(page);
page.on('console', console => {
this.consoleMsgs.push(console);
@@ -863,6 +872,10 @@ export class ControllablePage extends ControllableBase {
return result as Dict;
};
+ public getPage = () => {
+ return this;
+ };
+
private dismissActiveAlerts = async (): Promise => {
const activeAlerts = this.alerts.filter(a => a.active);
for (const alert of activeAlerts) {
@@ -879,12 +892,20 @@ export class ControllablePage extends ControllableBase {
}
export class ControllableFrame extends ControllableBase {
+ public target: Frame;
public frame: Frame;
- public constructor(frame: Frame) {
+ public constructor(
+ frame: Frame,
+ private page: ControllablePage
+ ) {
super(frame);
this.frame = frame;
}
+
+ public getPage = () => {
+ return this.page;
+ };
}
export type Controllable = ControllableFrame | ControllablePage;
diff --git a/test/source/tests/compose.ts b/test/source/tests/compose.ts
index 3a6da930f8c..9182691da95 100644
--- a/test/source/tests/compose.ts
+++ b/test/source/tests/compose.ts
@@ -156,7 +156,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
const forgottenPassphrase = 'this passphrase is forgotten';
await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultipleSmimeCEA2D53BB9D24871, forgottenPassphrase, {}, false);
const inboxPage = await browser.newExtensionInboxPage(t, acctEmail);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
await inboxPage.close();
await composePage.waitAndType('@input-password', forgottenPassphrase);
await composePage.waitAndClick('@action-send', { delay: 1 });
@@ -1254,7 +1254,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
false
);
const inboxPage = await browser.newPage(t, t.context.urls?.extensionInbox(acctEmail) + '&labelId=DRAFT&debug=___cu_true___');
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
const inboxTabId = await PageRecipe.getTabId(inboxPage);
// send message from a different tab
await PageRecipe.sendMessage(settingsPage, {
@@ -2151,7 +2151,7 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
const passphrase = 'pa$$w0rd';
await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultipleSmimeCEA2D53BB9D24871, passphrase, {}, false);
const inboxPage = await browser.newExtensionInboxPage(t, acctEmail);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
const composeFrame = await InboxPageRecipe.openAndGetComposeFrame(inboxPage);
await ComposePageRecipe.fillMsg(
composeFrame,
@@ -3043,7 +3043,10 @@ export const defineComposeTests = (testVariant: TestVariant, testWithBrowser: Te
await Promise.all(
attachmentFrames
.filter(f => f.url().includes('attachment.htm'))
- .map(async frame => await PageRecipe.getElementPropertyJson(await new ControllableFrame(frame).waitAny('@attachment-name'), 'textContent'))
+ .map(
+ async frame =>
+ await PageRecipe.getElementPropertyJson(await new ControllableFrame(frame, composePage).waitAny('@attachment-name'), 'textContent')
+ )
)
).to.eql(['small.txt.pgp', 'small.pdf.pgp']);
})
diff --git a/test/source/tests/decrypt.ts b/test/source/tests/decrypt.ts
index e47171a5325..37483d0bcfb 100644
--- a/test/source/tests/decrypt.ts
+++ b/test/source/tests/decrypt.ts
@@ -214,7 +214,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '184a474fc1bd59b8',
- expectedContent: 'This message contained the actual encrypted text inside a "message" attachment.',
+ content: ['This message contained the actual encrypted text inside a "message" attachment.'],
});
})
);
@@ -247,7 +247,7 @@ export const defineDecryptTests = (testVariant: TestVariant, testWithBrowser: Te
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '17dbdf2425ac0f29',
- expectedContent: 'Documento anexo de prueba.docx',
+ content: ['Documento anexo de prueba.docx'],
});
})
);
@@ -1049,27 +1049,27 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
test(
'decrypt - by entering pass phrase + remember in session',
testWithBrowser(async (t, browser) => {
- const { acctEmail } = await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility');
+ const { acctEmail, authHdr } = await BrowserRecipe.setupCommonAcctWithAttester(t, browser, 'compatibility');
const pp = Config.key('flowcrypt.compatibility.1pp1').passphrase;
const threadId = '15f7f5630573be2d';
- const expectedContent = 'The International DUBLIN Literary Award is an international literary award';
+ const content = ['The International DUBLIN Literary Award is an international literary award'];
const settingsPage = await browser.newExtensionSettingsPage(t);
await SettingsPageRecipe.forgetAllPassPhrasesInStorage(settingsPage, pp);
+ const enterPp = {
+ passphrase: Config.key('flowcrypt.compatibility.1pp1').passphrase,
+ isForgetPpChecked: true,
+ isForgetPpHidden: false,
+ };
+ // 1. inbox page test
// requires pp entry
- await InboxPageRecipe.checkDecryptMsg(t, browser, {
- acctEmail,
- threadId,
- expectedContent,
- enterPp: {
- passphrase: Config.key('flowcrypt.compatibility.1pp1').passphrase,
- isForgetPpChecked: true,
- isForgetPpHidden: false,
- },
- });
+ await InboxPageRecipe.checkDecryptMsg(t, browser, { enterPp, content, acctEmail, threadId });
+ // now remembers pp in session
+ await InboxPageRecipe.checkDecryptMsg(t, browser, { acctEmail, threadId, content, finishSessionAfterTesting: true });
+ // 2. gmail page test
+ // requires pp entry
+ await BrowserRecipe.pgpBlockVerifyDecryptedContent(t, browser, threadId, { enterPp, content }, authHdr);
// now remembers pp in session
- await InboxPageRecipe.checkDecryptMsg(t, browser, { acctEmail, threadId, expectedContent });
- // Finish session and check if it's finished
- await InboxPageRecipe.checkFinishingSession(t, browser, acctEmail, threadId);
+ await BrowserRecipe.pgpBlockVerifyDecryptedContent(t, browser, threadId, { content, finishSessionAfterTesting: true }, authHdr);
})
);
@@ -2021,6 +2021,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile1).length).to.equal(1);
+ expect(Object.keys(downloadedFile1)[0]).to.match(/demo.*\.bat/);
await pgpBlockPage.waitAndClick('@preview-attachment');
const attachmentPreviewPage = await inboxPage.getFrame(['attachment_preview.htm']);
await attachmentPreviewPage.waitAndClick('@attachment-preview-download');
@@ -2032,6 +2033,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile2).length).to.equal(1);
+ expect(Object.keys(downloadedFile2)[0]).to.match(/demo.*\.bat/);
await inboxPage.close();
const inboxPage2 = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}&threadId=${threadId2}`);
const pgpBlockPage2 = await inboxPage2.getFrame(['pgp_block.htm']);
@@ -2044,6 +2046,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile3).length).to.equal(1);
+ expect(Object.keys(downloadedFile3)[0]).to.match(/demo.*\.bat/);
await inboxPage2.close();
const gmailPage = await browser.newPage(t, `${t.context.urls?.mockGmailUrl()}/${threadId}`, undefined, authHdr);
await gmailPage.waitAll('iframe');
@@ -2057,6 +2060,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile4).length).to.equal(1);
+ expect(Object.keys(downloadedFile4)[0]).to.match(/demo.*\.bat/);
const attachmentFrame = await gmailPage.getFrame(['attachment.htm']);
await attachmentFrame.waitAndClick('@attachment-container');
const attachmentPreviewPage2 = await gmailPage.getFrame(['attachment_preview.htm']);
@@ -2069,6 +2073,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile5).length).to.equal(1);
+ expect(Object.keys(downloadedFile5)[0]).to.match(/demo.*\.bat/);
await gmailPage.close();
const gmailPage2 = await browser.newPage(t, `${t.context.urls?.mockGmailUrl()}/${threadId2}`, undefined, authHdr);
const pgpBlockPage4 = await gmailPage2.getFrame(['pgp_block.htm']);
@@ -2081,6 +2086,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile6).length).to.equal(1);
+ expect(Object.keys(downloadedFile6)[0]).to.match(/demo.*\.bat/);
await gmailPage2.close();
// check warning modal for regular unencrypted attachment on FlowCrypt web extension page
const inboxPage3 = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}&threadId=${threadId3}`);
@@ -2093,6 +2099,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile7).length).to.equal(1);
+ expect(Object.keys(downloadedFile7)[0]).to.match(/sample.*\.bat/);
await attachmentFrame2.waitAndClick('@attachment-container');
const attachmentPreviewPage3 = await inboxPage3.getFrame(['attachment_preview.htm']);
await attachmentPreviewPage3.waitAndClick('@attachment-preview-download');
@@ -2103,6 +2110,7 @@ XZ8r4OC6sguP/yozWlkG+7dDxsgKQVBENeG6Lw==
})
);
expect(Object.entries(downloadedFile8).length).to.equal(1);
+ expect(Object.keys(downloadedFile8)[0]).to.match(/sample.*\.bat/);
})
);
}
diff --git a/test/source/tests/flaky.ts b/test/source/tests/flaky.ts
index 79c7b6ee607..214d9aed6d7 100644
--- a/test/source/tests/flaky.ts
+++ b/test/source/tests/flaky.ts
@@ -412,7 +412,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
);
await settingsPage.notPresent('.swal2-container');
const inboxPage = await browser.newExtensionInboxPage(t, acctEmail);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
const composeFrame = await InboxPageRecipe.openAndGetComposeFrame(inboxPage);
await ComposePageRecipe.fillMsg(composeFrame, { to: 'human@flowcrypt.com' }, 'should not send as pass phrase is not known', undefined, {
encrypt: false,
@@ -607,11 +607,11 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testkey0389D3A7, passphrase, {}, false);
await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultipleSmimeCEA2D53BB9D24871, passphrase, {}, false);
const inboxPage = await browser.newExtensionInboxPage(t, acctEmail);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '17c0e50966d7877c',
- expectedContent: '1st key of of 2 keys with the same passphrase',
+ content: ['1st key of of 2 keys with the same passphrase'],
enterPp: {
passphrase,
isForgetPpChecked: true,
@@ -621,7 +621,7 @@ export const defineFlakyTests = (testVariant: TestVariant, testWithBrowser: Test
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '17c0e55caaa4abb3',
- expectedContent: '2nd key of of 2 keys with the same passphrase',
+ content: ['2nd key of of 2 keys with the same passphrase'],
// passphrase for the 2nd key should not be needed because it's the same as for the 1st key
});
// as decrypted s/mime messages are not rendered yet (#4070), let's test signing instead
@@ -785,7 +785,7 @@ AfYUJUhqjgSuBctnpj0=
while (frames.length !== 50) {
frames = (inboxPage.target as Page).frames().filter(frame => frame.url().includes(frameName));
}
- await Promise.all(frames.map(frame => new ControllableFrame(frame).waitForSelTestState('ready', 60)));
+ await Promise.all(frames.map(frame => new ControllableFrame(frame, inboxPage).waitForSelTestState('ready', 60)));
const stop = new Date();
const diff = stop.getTime() - start.getTime();
expect(diff).to.be.lessThan(20000);
diff --git a/test/source/tests/page-recipe/inbox-page-recipe.ts b/test/source/tests/page-recipe/inbox-page-recipe.ts
index bebebc44b3e..9da579eded5 100644
--- a/test/source/tests/page-recipe/inbox-page-recipe.ts
+++ b/test/source/tests/page-recipe/inbox-page-recipe.ts
@@ -4,16 +4,14 @@ import { BrowserHandle, ControllableFrame, ControllablePage } from '../../browse
import { AvaContext } from '../tooling/';
import { PageRecipe } from './abstract-page-recipe';
-import { Util } from '../../util';
-import { expect } from 'chai';
+import { TestMessageAndSession, Util } from '../../util';
+import { BrowserRecipe } from '../tooling/browser-recipe';
type CheckDecryptMsg$opt = {
acctEmail: string;
threadId: string;
- expectedContent: string;
- finishCurrentSession?: boolean;
- enterPp?: { passphrase: string; isForgetPpHidden?: boolean; isForgetPpChecked?: boolean };
-};
+} & TestMessageAndSession;
+
type CheckSentMsg$opt = {
acctEmail: string;
subject: string;
@@ -24,68 +22,12 @@ type CheckSentMsg$opt = {
};
export class InboxPageRecipe extends PageRecipe {
- public static checkDecryptMsg = async (
- t: AvaContext,
- browser: BrowserHandle,
- { acctEmail, threadId, enterPp, expectedContent, finishCurrentSession }: CheckDecryptMsg$opt
- ) => {
- const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}&threadId=${threadId}`);
- await inboxPage.waitAll('iframe');
- if (finishCurrentSession) {
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
- await inboxPage.waitAll('iframe');
- }
- const pgpBlockFrame = await inboxPage.getFrame(['pgp_block.htm']);
- await pgpBlockFrame.waitAll('@pgp-block-content');
- await pgpBlockFrame.waitForSelTestState('ready');
- if (enterPp) {
- await inboxPage.notPresent('@action-finish-session');
- const errBadgeContent = await pgpBlockFrame.read('@pgp-error');
- expect(errBadgeContent).to.equal('pass phrase needed');
- await pgpBlockFrame.notPresent('@action-print');
- await pgpBlockFrame.waitAndClick('@action-show-passphrase-dialog', { delay: 1 });
- await inboxPage.waitAll('@dialog-passphrase');
- const ppFrame = await inboxPage.getFrame(['passphrase.htm']);
- await ppFrame.waitAndType('@input-pass-phrase', enterPp.passphrase);
- if (enterPp.isForgetPpHidden !== undefined) {
- expect(await ppFrame.hasClass('@forget-pass-phrase-label', 'hidden')).to.equal(enterPp.isForgetPpHidden);
- }
- if (enterPp.isForgetPpChecked !== undefined) {
- expect(await ppFrame.isChecked('@forget-pass-phrase-checkbox')).to.equal(enterPp.isForgetPpChecked);
- }
- await ppFrame.waitAndClick('@action-confirm-pass-phrase-entry', { delay: 1 });
- await pgpBlockFrame.waitForSelTestState('ready');
- await inboxPage.waitAll('@action-finish-session');
- await Util.sleep(1);
- }
- await pgpBlockFrame.waitAll('@pgp-error', { visible: false });
- if (!(await pgpBlockFrame.isElementVisible('@action-print'))) {
- throw new Error(`Print button is invisible`);
- }
- const content = await pgpBlockFrame.read('@pgp-block-content');
- if (!content?.includes(expectedContent)) {
- throw new Error(`message did not decrypt`);
- }
+ public static checkDecryptMsg = async (t: AvaContext, browser: BrowserHandle, m: CheckDecryptMsg$opt) => {
+ const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${m.acctEmail}&threadId=${m.threadId}`);
+ await BrowserRecipe.checkDecryptMsgOnPage(t, inboxPage, m);
await inboxPage.close();
};
- public static finishSessionOnInboxPage = async (inboxPage: ControllablePage) => {
- await inboxPage.waitAndClick('@action-finish-session');
- await inboxPage.waitTillGone('@action-finish-session');
- await Util.sleep(3); // give frames time to reload, else we will be manipulating them while reloading -> Error: waitForFunction failed: frame got detached.
- };
-
- public static checkFinishingSession = async (t: AvaContext, browser: BrowserHandle, acctEmail: string, threadId: string) => {
- const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}&threadId=${threadId}`);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
- await inboxPage.waitAll('iframe');
- const pgpBlockFrame = await inboxPage.getFrame(['pgp_block.htm']);
- await pgpBlockFrame.waitAll('@pgp-block-content');
- await pgpBlockFrame.waitForSelTestState('ready');
- await pgpBlockFrame.waitAndClick('@action-show-passphrase-dialog', { delay: 1 });
- await inboxPage.waitAll('@dialog-passphrase');
- };
-
public static checkSentMsg = async (
t: AvaContext,
browser: BrowserHandle,
diff --git a/test/source/tests/settings.ts b/test/source/tests/settings.ts
index 5687d701bbf..63227f56f2a 100644
--- a/test/source/tests/settings.ts
+++ b/test/source/tests/settings.ts
@@ -561,7 +561,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '16819bec18d4e011',
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
})
);
@@ -585,7 +585,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
isForgetPpChecked: true,
isForgetPpHidden: false,
},
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
// change pp - should not ask for pp because already in session
await SettingsPageRecipe.changePassphrase(settingsPage, undefined, newPp);
@@ -593,19 +593,19 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '16819bec18d4e011',
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
// test decrypt - should ask for new pass phrase
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '16819bec18d4e011',
- finishCurrentSession: true,
+ finishSessionBeforeTesting: true,
enterPp: {
passphrase: newPp,
isForgetPpChecked: true,
isForgetPpHidden: false,
},
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
})
);
@@ -638,9 +638,9 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '179f6feb575df213',
- finishCurrentSession: true,
+ finishSessionBeforeTesting: true,
enterPp: { passphrase, isForgetPpHidden: true, isForgetPpChecked: true },
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
const { cryptup_userforbidstoringpassphraseclientconfigurationflowcrypttest_passphrase_B8F687BCDE14435A: savedPassphrase2 } =
await settingsPage.getFromLocalStorage(['cryptup_userforbidstoringpassphraseclientconfigurationflowcrypttest_passphrase_B8F687BCDE14435A']);
@@ -654,15 +654,15 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '179f6feb575df213',
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
// test decrypt - should ask for new pass phrase
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '179f6feb575df213',
- finishCurrentSession: true,
+ finishSessionBeforeTesting: true,
enterPp: { passphrase: newPp, isForgetPpHidden: true, isForgetPpChecked: true },
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
})
);
@@ -683,14 +683,14 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '16819bec18d4e011',
- expectedContent: 'changed correctly if this can be decrypted',
+ content: ['changed correctly if this can be decrypted'],
});
// test decrypt - should ask for new pass phrase
await InboxPageRecipe.checkDecryptMsg(t, browser, {
acctEmail,
threadId: '16819bec18d4e011',
- expectedContent: 'changed correctly if this can be decrypted',
- finishCurrentSession: true,
+ content: ['changed correctly if this can be decrypted'],
+ finishSessionBeforeTesting: true,
enterPp: { passphrase: newPp, isForgetPpChecked: true, isForgetPpHidden: false },
});
})
@@ -1207,7 +1207,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
);
await settingsPage.close();
const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
await SettingsPageRecipe.addKeyTest(t, browser, acctEmail, testConstants.testKeyMultiple98acfa1eadab5b92, '1234', {
isSavePassphraseChecked: true,
isSavePassphraseHidden: false,
@@ -1264,7 +1264,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
{ isSavePassphraseChecked: false, isSavePassphraseHidden: false }
);
const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
await inboxPage.close();
const key98acfa1eadab5b92 = await KeyUtil.parse(testConstants.testKeyMultiple98acfa1eadab5b92);
expect(await KeyUtil.decrypt(key98acfa1eadab5b92, '1234')).to.equal(true);
@@ -1307,7 +1307,7 @@ export const defineSettingsTests = (testVariant: TestVariant, testWithBrowser: T
{ isSavePassphraseChecked: false, isSavePassphraseHidden: false }
);
const inboxPage = await browser.newExtensionPage(t, `chrome/settings/inbox/inbox.htm?acctEmail=${acctEmail}`);
- await InboxPageRecipe.finishSessionOnInboxPage(inboxPage);
+ await BrowserRecipe.finishSession(inboxPage);
await inboxPage.close();
await settingsPage.waitAndClick('@action-open-backup-page');
const backupFrame = await settingsPage.getFrame(['backup.htm']);
diff --git a/test/source/tests/setup.ts b/test/source/tests/setup.ts
index fbd436c0580..53d7eabcf2e 100644
--- a/test/source/tests/setup.ts
+++ b/test/source/tests/setup.ts
@@ -12,7 +12,6 @@ import { Str, emailKeyIndex } from './../core/common';
import { BrowserRecipe } from './tooling/browser-recipe';
import { Key, KeyInfoWithIdentity, KeyUtil } from '../core/crypto/key';
import { testConstants } from './tooling/consts';
-import { InboxPageRecipe } from './page-recipe/inbox-page-recipe';
import { PageRecipe } from './page-recipe/abstract-page-recipe';
import { BrowserHandle, ControllablePage } from '../browser';
import { OauthPageRecipe } from './page-recipe/oauth-page-recipe';
@@ -1488,7 +1487,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
const set4 = await retrieveAndCheckKeys(settingsPage, acct, 1);
expect(set4[0].lastModified).to.equal(set3[0].lastModified); // no update
// 4. Forget the passphrase, EKM the same version of the existing key, no prompt
- await InboxPageRecipe.finishSessionOnInboxPage(gmailPage);
+ await BrowserRecipe.finishSession(gmailPage);
await gmailPage.close();
gmailPage = await browser.newMockGmailPage(t, extraAuthHeaders);
await PageRecipe.noToastAppears(gmailPage);
@@ -1552,7 +1551,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
expect(mainKey10[0].lastModified!).to.be.greaterThan(mainKey9[0].lastModified!); // updated this key
// 10. Forget the passphrase, EKM returns a third key, we enter a passphrase that doesn't match any of the existing keys, no update
- await InboxPageRecipe.finishSessionOnInboxPage(gmailPage);
+ await BrowserRecipe.finishSession(gmailPage);
await gmailPage.close();
t.context.mockApi!.configProvider.config.ekm!.keys = [testConstants.unprotectedPrvKey];
gmailPage = await browser.newMockGmailPage(t, extraAuthHeaders);
@@ -1587,7 +1586,7 @@ AN8G3r5Htj8olot+jm9mIa5XLXWzMNUZgg==
const mainKey12 = KeyUtil.filterKeysByIdentity(set12, [{ family: 'openpgp', id: '277D1ADA213881F4ABE0415395E783DC0289E2E2' }]);
expect(mainKey12.length).to.equal(1);
// 12. Forget the passphrase, EKM sends a broken key, no passphrase dialog, no updates
- await InboxPageRecipe.finishSessionOnInboxPage(gmailPage);
+ await BrowserRecipe.finishSession(gmailPage);
await gmailPage.close();
t.context.mockApi!.configProvider.config.ekm!.keys = [
await updateAndArmorKey(set2[0]),
diff --git a/test/source/tests/tooling/browser-recipe.ts b/test/source/tests/tooling/browser-recipe.ts
index c29075c3c8d..9008fa1bbc4 100644
--- a/test/source/tests/tooling/browser-recipe.ts
+++ b/test/source/tests/tooling/browser-recipe.ts
@@ -1,6 +1,6 @@
/* ©️ 2016 - present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com */
-import { Config, Util, TestMessage } from '../../util';
+import { Config, Util, TestMessage, TestMessageAndSession } from '../../util';
import { AvaContext } from '.';
import { BrowserHandle, Controllable, ControllableFrame, ControllablePage } from '../../browser';
@@ -255,34 +255,86 @@ export class BrowserRecipe {
return { acctEmail, passphrase: key.passphrase, settingsPage };
};
+ // todo: move to gmail-page-recipe
public static pgpBlockVerifyDecryptedContent = async (
t: AvaContext,
browser: BrowserHandle,
msgId: string,
- m: TestMessage,
+ m: TestMessageAndSession,
extraHeaders: Record
) => {
const gmailPage = await browser.newPage(t, `${t.context.urls?.mockGmailUrl()}/${msgId}`, undefined, extraHeaders);
- await gmailPage.waitAll('iframe');
- await BrowserRecipe.pgpBlockCheck(t, await gmailPage.getFrame(['pgp_block.htm']), m);
+ await BrowserRecipe.checkDecryptMsgOnPage(t, gmailPage, m);
await gmailPage.close();
};
- public static pgpBlockCheck = async (t: AvaContext, pgpBlockPage: ControllableFrame, m: TestMessage) => {
+ // todo: move some of these helpers somewhere to page-recipe/...
+ // gmail or inbox
+ public static checkDecryptMsgOnPage = async (t: AvaContext, page: ControllablePage, m: TestMessageAndSession) => {
+ await page.waitAll('iframe');
+ if (m.finishSessionBeforeTesting) {
+ await BrowserRecipe.finishSession(page);
+ await page.waitAll('iframe');
+ }
+ await BrowserRecipe.pgpBlockCheck(t, await page.getFrame(['pgp_block.htm']), m);
+ if (m.finishSessionAfterTesting) {
+ await BrowserRecipe.finishSession(page);
+ await page.waitAll('iframe');
+ const pgpBlockFrame = await page.getFrame(['pgp_block.htm']);
+ await pgpBlockFrame.waitAll('@pgp-block-content');
+ await pgpBlockFrame.waitForSelTestState('ready');
+ await pgpBlockFrame.waitAndClick('@action-show-passphrase-dialog', { delay: 1 });
+ await page.waitAll('@dialog-passphrase');
+ }
+ };
+
+ // gmail or inbox
+ public static finishSession = async (page: ControllablePage) => {
+ await page.waitAndClick('@action-finish-session');
+ await page.waitTillGone('@action-finish-session');
+ await Util.sleep(3); // give frames time to reload, else we will be manipulating them while reloading -> Error: waitForFunction failed: frame got detached.
+ };
+
+ // todo: move to page-recipe/pgp-block-frame-recipe or frame-recipe/pgp-block-frame-recipe?
+ public static pgpBlockCheck = async (t: AvaContext, pgpBlockFrame: ControllableFrame, m: TestMessage) => {
if (m.expectPercentageProgress) {
- await pgpBlockPage.waitForContent('@pgp-block-content', /Retrieving message... \d+%/, 20, 10);
+ await pgpBlockFrame.waitForContent('@pgp-block-content', /Retrieving message... \d+%/, 20, 10);
+ } else {
+ await pgpBlockFrame.waitAll('@pgp-block-content');
}
- await pgpBlockPage.waitForSelTestState('ready', 100);
+ await pgpBlockFrame.waitForSelTestState('ready', 100);
await Util.sleep(1);
+ if (m.enterPp) {
+ const page = pgpBlockFrame.getPage();
+ await page.notPresent('@action-finish-session');
+ const errBadgeContent = await pgpBlockFrame.read('@pgp-error');
+ expect(errBadgeContent).to.equal('pass phrase needed');
+ await pgpBlockFrame.notPresent('@action-print');
+ await pgpBlockFrame.waitAndClick('@action-show-passphrase-dialog', { delay: 1 });
+ await page.waitAll('@dialog-passphrase');
+ const ppFrame = await page.getFrame(['passphrase.htm']);
+ await ppFrame.waitAndType('@input-pass-phrase', m.enterPp.passphrase);
+ if (m.enterPp.isForgetPpHidden !== undefined) {
+ expect(await ppFrame.hasClass('@forget-pass-phrase-label', 'hidden')).to.equal(m.enterPp.isForgetPpHidden);
+ }
+ if (m.enterPp.isForgetPpChecked !== undefined) {
+ expect(await ppFrame.isChecked('@forget-pass-phrase-checkbox')).to.equal(m.enterPp.isForgetPpChecked);
+ }
+ await ppFrame.waitAndClick('@action-confirm-pass-phrase-entry', { delay: 1 });
+ await pgpBlockFrame.waitForSelTestState('ready');
+ await page.waitAll('@action-finish-session'); // todo: gmail
+ await Util.sleep(1);
+ }
+
if (m.quoted) {
- await pgpBlockPage.waitAndClick('@action-show-quoted-content');
+ await pgpBlockFrame.waitAndClick('@action-show-quoted-content');
await Util.sleep(1);
} else {
- if (await pgpBlockPage.isElementPresent('@action-show-quoted-content')) {
+ if (await pgpBlockFrame.isElementPresent('@action-show-quoted-content')) {
throw new Error(`element: @action-show-quoted-content not expected in: ${t.title}`);
}
}
- const content = await pgpBlockPage.read('@pgp-block-content');
+ const content = await pgpBlockFrame.read('@pgp-block-content');
for (const expectedContent of m.content) {
if (!content?.includes(expectedContent)) {
throw new Error(`pgp_block_verify_decrypted_content:missing expected content: ${expectedContent}` + `\nactual content: ${content}`);
@@ -296,8 +348,8 @@ export class BrowserRecipe {
}
}
}
- const sigBadgeContent = await pgpBlockPage.read('@pgp-signature');
- const encBadgeContent = await pgpBlockPage.read('@pgp-encryption');
+ const sigBadgeContent = await pgpBlockFrame.read('@pgp-signature');
+ const encBadgeContent = await pgpBlockFrame.read('@pgp-encryption');
if (m.signature) {
// todo: check color, 'signed' should have 'green_label' class without 'red_label', others should have 'red_label' class
if (sigBadgeContent !== m.signature) {
@@ -320,15 +372,18 @@ export class BrowserRecipe {
if (m.error) {
expect(sigBadgeContent).to.be.empty;
expect(encBadgeContent).to.be.empty;
- await pgpBlockPage.notPresent('@action-print');
- const errBadgeContent = await pgpBlockPage.read('@pgp-error');
+ await pgpBlockFrame.notPresent('@action-print');
+ const errBadgeContent = await pgpBlockFrame.read('@pgp-error');
if (errBadgeContent !== m.error) {
t.log(`found err content:${errBadgeContent}`);
throw new Error(`pgp_block_verify_decrypted_content:missing expected error content:${m.error}`);
}
- } else if (m.content.length > 0) {
- if (!(await pgpBlockPage.isElementVisible('@action-print'))) {
- throw new Error(`Print button is invisible`);
+ } else {
+ await pgpBlockFrame.waitAll('@pgp-error', { visible: false });
+ if (m.content.length > 0) {
+ if (!(await pgpBlockFrame.isElementVisible('@action-print'))) {
+ throw new Error(`Print button is invisible`);
+ }
}
}
};
diff --git a/test/source/util/index.ts b/test/source/util/index.ts
index dd61edefb1e..9c9657e4211 100644
--- a/test/source/util/index.ts
+++ b/test/source/util/index.ts
@@ -44,6 +44,12 @@ export type TestMessage = {
signature?: string;
encryption?: string;
error?: string;
+ enterPp?: { passphrase: string; isForgetPpHidden?: boolean; isForgetPpChecked?: boolean };
+};
+
+export type TestMessageAndSession = TestMessage & {
+ finishSessionBeforeTesting?: boolean; // finish session before testing pgp_block
+ finishSessionAfterTesting?: boolean; // finish session after testing pgp_block and test that pgp_block now requires a passphrase
};
export type TestKeyInfo = {