summaryrefslogtreecommitdiffstats
path: root/comm/mail/actors
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/actors')
-rw-r--r--comm/mail/actors/ChatActionChild.sys.mjs55
-rw-r--r--comm/mail/actors/ChatActionParent.sys.mjs32
-rw-r--r--comm/mail/actors/ContextMenuParent.sys.mjs45
-rw-r--r--comm/mail/actors/LinkClickHandlerChild.jsm178
-rw-r--r--comm/mail/actors/LinkClickHandlerParent.jsm30
-rw-r--r--comm/mail/actors/LinkHandlerParent.sys.mjs57
-rw-r--r--comm/mail/actors/MailLinkChild.jsm42
-rw-r--r--comm/mail/actors/MailLinkParent.jsm96
-rw-r--r--comm/mail/actors/PromptParent.jsm180
-rw-r--r--comm/mail/actors/VCardChild.jsm23
-rw-r--r--comm/mail/actors/VCardParent.jsm17
-rw-r--r--comm/mail/actors/moz.build24
12 files changed, 779 insertions, 0 deletions
diff --git a/comm/mail/actors/ChatActionChild.sys.mjs b/comm/mail/actors/ChatActionChild.sys.mjs
new file mode 100644
index 0000000000..92af66d3df
--- /dev/null
+++ b/comm/mail/actors/ChatActionChild.sys.mjs
@@ -0,0 +1,55 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class ChatActionChild extends JSWindowActorChild {
+ constructor() {
+ super();
+
+ this.messageActions = null;
+ }
+
+ receiveMessage(message) {
+ if (!this.messageActions) {
+ return;
+ }
+ if (message.name === "ChatAction:Run") {
+ this.messageActions[message.data.index].run();
+ } else if (message.name === "ChatAction:Hide") {
+ this.messageActions = null;
+ }
+ }
+
+ async handleEvent(event) {
+ let node = event.composedTarget;
+
+ // Set the node to containing <video>/<audio>/<embed>/<object> if the node
+ // is in the videocontrols UA Widget.
+ if (node.containingShadowRoot?.isUAWidget()) {
+ const host = node.containingShadowRoot.host;
+ if (
+ this.contentWindow.HTMLMediaElement.isInstance(host) ||
+ this.contentWindow.HTMLEmbedElement.isInstance(host) ||
+ this.contentWindow.HTMLObjectElement.isInstance(host)
+ ) {
+ node = host;
+ }
+ }
+
+ while (node) {
+ if (node._originalMsg) {
+ this.messageActions = node._originalMsg.getActions();
+ break;
+ }
+ node = node.parentNode;
+ }
+ if (!this.messageActions) {
+ return;
+ }
+ this.sendAsyncMessage("ChatAction:Actions", {
+ actions: this.messageActions.map(action => action.label),
+ });
+ }
+}
diff --git a/comm/mail/actors/ChatActionParent.sys.mjs b/comm/mail/actors/ChatActionParent.sys.mjs
new file mode 100644
index 0000000000..15b67419f5
--- /dev/null
+++ b/comm/mail/actors/ChatActionParent.sys.mjs
@@ -0,0 +1,32 @@
+/* -*- mode: js; indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 sw=2 sts=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class ChatActionParent extends JSWindowActorParent {
+ receiveMessage(message) {
+ if (message.name === "ChatAction:Actions") {
+ let browser = this.manager.rootFrameLoader.ownerElement;
+ if (browser.contentWindow?.gChatContextMenu) {
+ browser.contentWindow.gChatContextMenu.initActions(
+ message.data.actions
+ );
+ return;
+ }
+
+ // Otherwise, send them to the outer window.
+ let win = browser.ownerGlobal;
+ if (win.gChatContextMenu) {
+ win.gChatContextMenu.initActions(message.data.actions);
+ return;
+ }
+ this.actions = message.data.actions;
+ }
+ }
+
+ reportHide() {
+ this.sendAsyncMessage("ChatAction:Hide");
+ this.actions = null;
+ }
+}
diff --git a/comm/mail/actors/ContextMenuParent.sys.mjs b/comm/mail/actors/ContextMenuParent.sys.mjs
new file mode 100644
index 0000000000..5cf1d5dbe2
--- /dev/null
+++ b/comm/mail/actors/ContextMenuParent.sys.mjs
@@ -0,0 +1,45 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class ContextMenuParent extends JSWindowActorParent {
+ receiveMessage(message) {
+ if (message.name != "contextmenu") {
+ return;
+ }
+
+ let browser = this.manager.rootFrameLoader.ownerElement;
+ let win = browser.ownerGlobal.top;
+
+ // Send events from a message display browser to about:3pane or
+ // about:message if possible.
+ let tabmail = win.document.getElementById("tabmail");
+ if (tabmail) {
+ let chromeBrowser = tabmail.currentTabInfo.chromeBrowser;
+ if (
+ chromeBrowser?.contentWindow.openContextMenu(message, browser, this)
+ ) {
+ return;
+ }
+ }
+ let messageBrowser = win.document.getElementById("messageBrowser");
+ if (messageBrowser?.contentWindow.openContextMenu(message, browser, this)) {
+ return;
+ }
+
+ // Otherwise, send them to the outer window.
+ if ("openContextMenu" in win) {
+ win.openContextMenu(message, browser, this);
+ }
+ }
+
+ hiding() {
+ try {
+ this.sendAsyncMessage("ContextMenu:Hiding", {});
+ } catch (e) {
+ // This will throw if the content goes away while the
+ // context menu is still open.
+ }
+ }
+}
diff --git a/comm/mail/actors/LinkClickHandlerChild.jsm b/comm/mail/actors/LinkClickHandlerChild.jsm
new file mode 100644
index 0000000000..8ad42a7a23
--- /dev/null
+++ b/comm/mail/actors/LinkClickHandlerChild.jsm
@@ -0,0 +1,178 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = [
+ "LinkClickHandlerChild",
+ "StrictLinkClickHandlerChild",
+];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyServiceGetter(
+ lazy,
+ "protocolSvc",
+ "@mozilla.org/uriloader/external-protocol-service;1",
+ "nsIExternalProtocolService"
+);
+
+/**
+ * Extract the href from the link click event.
+ * We look for HTMLAnchorElement, HTMLAreaElement, HTMLLinkElement,
+ * HTMLInputElement.form.action, and nested anchor tags.
+ * If the clicked element was a HTMLInputElement or HTMLButtonElement
+ * we return the form action.
+ *
+ * @returns the url and the text for the link being clicked.
+ */
+function hRefForClickEvent(aEvent) {
+ let target = aEvent.target;
+
+ if (
+ HTMLImageElement.isInstance(target) &&
+ target.hasAttribute("overflowing")
+ ) {
+ // Click on zoomed image.
+ return [null, null];
+ }
+
+ let href = null;
+ if (
+ HTMLAnchorElement.isInstance(target) ||
+ HTMLAreaElement.isInstance(target) ||
+ HTMLLinkElement.isInstance(target)
+ ) {
+ if (target.hasAttribute("href") && !target.download) {
+ href = target.href;
+ }
+ } else {
+ // We may be nested inside of a link node.
+ let linkNode = aEvent.target;
+ while (linkNode && !HTMLAnchorElement.isInstance(linkNode)) {
+ linkNode = linkNode.parentNode;
+ }
+
+ if (linkNode && !linkNode.download) {
+ href = linkNode.href;
+ }
+ }
+ return href;
+}
+
+/**
+ * Listens for click events and, if the click would result in loading a page
+ * on a different base domain from the current page, cancels the click event,
+ * redirecting the URI to an external browser, effectively creating a
+ * single-site browser.
+ *
+ * This actor applies to browsers in the "single-site" message manager group.
+ */
+class LinkClickHandlerChild extends JSWindowActorChild {
+ handleEvent(event) {
+ // Don't handle events that:
+ // a) are in the parent process (handled by onclick),
+ // b) aren't trusted,
+ // c) have already been handled or
+ // d) aren't left-click.
+ if (
+ this.manager.isInProcess ||
+ !event.isTrusted ||
+ event.defaultPrevented ||
+ event.button
+ ) {
+ return;
+ }
+
+ let eventHRef = hRefForClickEvent(event);
+ if (!eventHRef) {
+ return;
+ }
+
+ let pageURI = Services.io.newURI(this.document.location.href);
+ let eventURI = Services.io.newURI(eventHRef);
+
+ try {
+ if (pageURI.host == eventURI.host) {
+ // Avoid using the eTLD service, and this also works for IP addresses.
+ return;
+ }
+
+ try {
+ if (
+ Services.eTLD.getBaseDomain(eventURI) ==
+ Services.eTLD.getBaseDomain(pageURI)
+ ) {
+ return;
+ }
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+ console.error(ex);
+ }
+ }
+ } catch (ex) {
+ // The page or link might be from a host-less URL scheme such as about,
+ // blob, or data. The host is never going to match, carry on.
+ }
+
+ if (
+ !lazy.protocolSvc.isExposedProtocol(eventURI.scheme) ||
+ eventURI.schemeIs("http") ||
+ eventURI.schemeIs("https")
+ ) {
+ event.preventDefault();
+ this.sendAsyncMessage("openLinkExternally", eventHRef);
+ }
+ }
+}
+
+/**
+ * Listens for click events and, if the click would result in loading a
+ * different page from the current page, cancels the click event, redirecting
+ * the URI to an external browser, effectively creating a single-page browser.
+ *
+ * This actor applies to browsers in the "single-page" message manager group.
+ */
+class StrictLinkClickHandlerChild extends JSWindowActorChild {
+ handleEvent(event) {
+ // Don't handle events that:
+ // a) are in the parent process (handled by onclick),
+ // b) aren't trusted,
+ // c) have already been handled or
+ // d) aren't left-click.
+ if (
+ this.manager.isInProcess ||
+ !event.isTrusted ||
+ event.defaultPrevented ||
+ event.button
+ ) {
+ return;
+ }
+
+ let eventHRef = hRefForClickEvent(event);
+ if (!eventHRef) {
+ return;
+ }
+
+ let pageURI = Services.io.newURI(this.document.location.href);
+ let eventURI = Services.io.newURI(eventHRef);
+ if (eventURI.specIgnoringRef == pageURI.specIgnoringRef) {
+ return;
+ }
+
+ if (
+ !lazy.protocolSvc.isExposedProtocol(eventURI.scheme) ||
+ eventURI.schemeIs("http") ||
+ eventURI.schemeIs("https")
+ ) {
+ event.preventDefault();
+ this.sendAsyncMessage("openLinkExternally", eventHRef);
+ }
+ }
+}
diff --git a/comm/mail/actors/LinkClickHandlerParent.jsm b/comm/mail/actors/LinkClickHandlerParent.jsm
new file mode 100644
index 0000000000..5fc0dd2b90
--- /dev/null
+++ b/comm/mail/actors/LinkClickHandlerParent.jsm
@@ -0,0 +1,30 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = [
+ "LinkClickHandlerParent",
+ "StrictLinkClickHandlerParent",
+];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyScriptGetter(
+ // eslint-disable-next-line mozilla/reject-global-this
+ this,
+ "openLinkExternally",
+ "chrome://browser/content/utilityOverlay.js"
+);
+
+class LinkClickHandlerParent extends JSWindowActorParent {
+ receiveMessage({ data }) {
+ openLinkExternally(data);
+ }
+}
+
+class StrictLinkClickHandlerParent extends LinkClickHandlerParent {}
diff --git a/comm/mail/actors/LinkHandlerParent.sys.mjs b/comm/mail/actors/LinkHandlerParent.sys.mjs
new file mode 100644
index 0000000000..269405c4ff
--- /dev/null
+++ b/comm/mail/actors/LinkHandlerParent.sys.mjs
@@ -0,0 +1,57 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+export class LinkHandlerParent extends JSWindowActorParent {
+ receiveMessage(msg) {
+ let browser = this.browsingContext.top.embedderElement;
+ if (!browser) {
+ return;
+ }
+
+ switch (msg.name) {
+ case "Link:SetIcon":
+ this.setIconFromLink(browser, msg.data.iconURL, msg.data.canUseForTab);
+ break;
+ }
+ }
+
+ setIconFromLink(browser, iconURL, canUseForTab) {
+ let tabmail = browser.ownerDocument.getElementById("tabmail");
+ if (!tabmail) {
+ return;
+ }
+
+ let tab = tabmail.getTabForBrowser(browser);
+ if (tab?.mode?.type != "contentTab") {
+ return;
+ }
+
+ let iconURI;
+ try {
+ iconURI = Services.io.newURI(iconURL);
+ } catch (ex) {
+ console.error(ex);
+ return;
+ }
+ if (iconURI.scheme != "data") {
+ try {
+ Services.scriptSecurityManager.checkLoadURIWithPrincipal(
+ browser.contentPrincipal,
+ iconURI,
+ Services.scriptSecurityManager.ALLOW_CHROME
+ );
+ } catch (ex) {
+ return;
+ }
+ }
+
+ if (canUseForTab) {
+ tabmail.setTabFavIcon(
+ tab,
+ iconURL,
+ "chrome://messenger/skin/icons/new/compact/draft.svg"
+ );
+ }
+ }
+}
diff --git a/comm/mail/actors/MailLinkChild.jsm b/comm/mail/actors/MailLinkChild.jsm
new file mode 100644
index 0000000000..401b869fad
--- /dev/null
+++ b/comm/mail/actors/MailLinkChild.jsm
@@ -0,0 +1,42 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["MailLinkChild"];
+
+const PROTOCOLS = ["mailto:", "mid:", "news:", "snews:"];
+
+class MailLinkChild extends JSWindowActorChild {
+ handleEvent(event) {
+ let href = event.target.href;
+ let location = this.document.location;
+ if (
+ !href ||
+ // Do nothing if not the main button clicked.
+ event.button > 0 ||
+ // Do nothing if in the compose window.
+ location.href == "about:blank?compose"
+ ) {
+ return;
+ }
+
+ let url = new URL(href);
+ let protocol = url.protocol;
+ if (
+ PROTOCOLS.includes(protocol) ||
+ // A link to an attachment, e.g. cid: link.
+ (["imap:", "mailbox:"].includes(protocol) &&
+ url.searchParams.get("part") &&
+ // Prevent opening new tab for internal pdf link.
+ (!location.search.includes("part=") ||
+ url.origin != location.origin ||
+ url.pathname != location.pathname))
+ ) {
+ this.sendAsyncMessage(protocol, href);
+ event.preventDefault();
+ }
+ }
+}
diff --git a/comm/mail/actors/MailLinkParent.jsm b/comm/mail/actors/MailLinkParent.jsm
new file mode 100644
index 0000000000..6c371658f6
--- /dev/null
+++ b/comm/mail/actors/MailLinkParent.jsm
@@ -0,0 +1,96 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["MailLinkParent"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ AttachmentInfo: "resource:///modules/AttachmentInfo.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailServices: "resource:///modules/MailServices.jsm",
+ MailUtils: "resource:///modules/MailUtils.jsm",
+});
+
+class MailLinkParent extends JSWindowActorParent {
+ receiveMessage(value) {
+ switch (value.name) {
+ case "imap:":
+ case "mailbox:":
+ this._handleMailboxLink(value);
+ break;
+ case "mailto:":
+ this._handleMailToLink(value);
+ break;
+ case "mid:":
+ this._handleMidLink(value);
+ break;
+ case "news:":
+ case "snews:":
+ this._handleNewsLink(value);
+ break;
+ default:
+ throw Components.Exception(
+ `Unsupported name=${value.name} url=${value.data}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ }
+
+ _handleMailboxLink({ data, target }) {
+ // AttachmentInfo is defined in msgHdrView.js.
+ let url = new URL(data);
+ new lazy.AttachmentInfo({
+ contentType: "",
+ url: data,
+ name: url.searchParams.get("filename"),
+ uri: "",
+ isExternalAttachment: false,
+ }).open(target.browsingContext.topChromeWindow, target.browsingContext.id);
+ }
+
+ _handleMailToLink({ data, target }) {
+ let identity = null;
+
+ // If the document with the link is a message, try to get the identity
+ // from the message and use it when composing.
+ let documentURI = target.windowContext.documentURI;
+ if (documentURI instanceof Ci.nsIMsgMessageUrl) {
+ documentURI.QueryInterface(Ci.nsIMsgMessageUrl);
+ [identity] = lazy.MailUtils.getIdentityForHeader(
+ documentURI.messageHeader
+ );
+ }
+
+ lazy.MailServices.compose.OpenComposeWindowWithURI(
+ undefined,
+ Services.io.newURI(data),
+ identity
+ );
+ }
+
+ _handleMidLink({ data }) {
+ // data is the mid: url.
+ lazy.MailUtils.openMessageByMessageId(data.slice(4));
+ }
+
+ _handleNewsLink({ data }) {
+ Services.ww.openWindow(
+ null,
+ "chrome://messenger/content/messageWindow.xhtml",
+ "_blank",
+ "all,chrome,dialog=no,status,toolbar",
+ Services.io.newURI(data)
+ );
+ }
+}
diff --git a/comm/mail/actors/PromptParent.jsm b/comm/mail/actors/PromptParent.jsm
new file mode 100644
index 0000000000..5aedf5a1b9
--- /dev/null
+++ b/comm/mail/actors/PromptParent.jsm
@@ -0,0 +1,180 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["PromptParent"];
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PromptUtils: "resource://gre/modules/PromptUtils.sys.mjs",
+});
+
+/**
+ * @typedef {object} Prompt
+ * @property {Function} resolver
+ * The resolve function to be called with the data from the Prompt
+ * after the user closes it.
+ * @property {object} tabModalPrompt
+ * The TabModalPrompt being shown to the user.
+ */
+
+/**
+ * gBrowserPrompts weakly maps BrowsingContexts to a Map of their currently
+ * active Prompts.
+ *
+ * @type {WeakMap<BrowsingContext, Prompt>}
+ */
+let gBrowserPrompts = new WeakMap();
+
+class PromptParent extends JSWindowActorParent {
+ didDestroy() {
+ // In the event that the subframe or tab crashed, make sure that
+ // we close any active Prompts.
+ this.forceClosePrompts();
+ }
+
+ /**
+ * Registers a new Prompt to be tracked for a particular BrowsingContext.
+ * We need to track a Prompt so that we can, for example, force-close the
+ * TabModalPrompt if the originating subframe or tab unloads or crashes.
+ *
+ * @param {object} tabModalPrompt
+ * The TabModalPrompt that will be shown to the user.
+ * @param {string} id
+ * A unique ID to differentiate multiple Prompts coming from the same
+ * BrowsingContext.
+ * @returns {Promise}
+ * @resolves {object}
+ * Resolves with the arguments returned from the TabModalPrompt when it
+ * is dismissed.
+ */
+ registerPrompt(tabModalPrompt, id) {
+ let prompts = gBrowserPrompts.get(this.browsingContext);
+ if (!prompts) {
+ prompts = new Map();
+ gBrowserPrompts.set(this.browsingContext, prompts);
+ }
+
+ let promise = new Promise(resolve => {
+ prompts.set(id, {
+ tabModalPrompt,
+ resolver: resolve,
+ });
+ });
+
+ return promise;
+ }
+
+ /**
+ * Removes a Prompt for a BrowsingContext with a particular ID from the registry.
+ * This needs to be done to avoid leaking <xul:browser>'s.
+ *
+ * @param {string} id
+ * A unique ID to differentiate multiple Prompts coming from the same
+ * BrowsingContext.
+ */
+ unregisterPrompt(id) {
+ let prompts = gBrowserPrompts.get(this.browsingContext);
+ if (prompts) {
+ prompts.delete(id);
+ }
+ }
+
+ /**
+ * Programmatically closes all Prompts for the current BrowsingContext.
+ */
+ forceClosePrompts() {
+ let prompts = gBrowserPrompts.get(this.browsingContext) || [];
+
+ for (let [, prompt] of prompts) {
+ prompt.tabModalPrompt && prompt.tabModalPrompt.abortPrompt();
+ }
+ }
+
+ receiveMessage(message) {
+ let args = message.data;
+
+ switch (message.name) {
+ case "Prompt:Open": {
+ return this.openWindowPrompt(args);
+ }
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Opens a window prompt for a BrowsingContext, and puts the associated
+ * browser in the modal state until the prompt is closed.
+ *
+ * @param {object} args
+ * The arguments passed up from the BrowsingContext to be passed
+ * directly to the modal window.
+ * @returns {Promise}
+ * Resolves when the window prompt is dismissed.
+ * @resolves {object}
+ * The arguments returned from the window prompt.
+ */
+ async openWindowPrompt(args) {
+ const COMMON_DIALOG = "chrome://global/content/commonDialog.xhtml";
+ const SELECT_DIALOG = "chrome://global/content/selectDialog.xhtml";
+ let uri = args.promptType == "select" ? SELECT_DIALOG : COMMON_DIALOG;
+
+ let browsingContext = this.browsingContext.top;
+
+ let browser = browsingContext.embedderElement;
+ let win;
+
+ // If we are a chrome actor we can use the associated chrome win.
+ if (!browsingContext.isContent && browsingContext.window) {
+ win = browsingContext.window;
+ } else {
+ win = browser?.ownerGlobal;
+ if (!win?.isChromeWindow) {
+ win = browsingContext.topChromeWindow;
+ }
+ }
+
+ // There's a requirement for prompts to be blocked if a window is
+ // passed and that window is hidden (eg, auth prompts are suppressed if the
+ // passed window is the hidden window).
+ // See bug 875157 comment 30 for more..
+ if (win?.winUtils && !win.winUtils.isParentWindowMainWidgetVisible) {
+ throw new Error("Cannot call openModalWindow on a hidden window");
+ }
+
+ try {
+ if (browser) {
+ // The compose editor does not support enter/leaveModalState.
+ browser.enterModalState?.();
+ lazy.PromptUtils.fireDialogEvent(
+ win,
+ "DOMWillOpenModalDialog",
+ browser
+ );
+ }
+
+ let bag = lazy.PromptUtils.objectToPropBag(args);
+
+ Services.ww.openWindow(
+ win,
+ uri,
+ "_blank",
+ "centerscreen,chrome,modal,titlebar",
+ bag
+ );
+
+ lazy.PromptUtils.propBagToObject(bag, args);
+ } finally {
+ if (browser) {
+ browser.leaveModalState?.();
+ lazy.PromptUtils.fireDialogEvent(win, "DOMModalDialogClosed", browser);
+ }
+ }
+ return args;
+ }
+}
diff --git a/comm/mail/actors/VCardChild.jsm b/comm/mail/actors/VCardChild.jsm
new file mode 100644
index 0000000000..db43b6c145
--- /dev/null
+++ b/comm/mail/actors/VCardChild.jsm
@@ -0,0 +1,23 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["VCardChild"];
+
+class VCardChild extends JSWindowActorChild {
+ handleEvent(event) {
+ // This link comes from VCardMimeConverter.convertToHTML in VCardUtils.jsm.
+ if (event.target.classList.contains("moz-vcard-badge")) {
+ if (event.button == 0) {
+ // The href is a data:text/vcard URL.
+ let href = event.target.href;
+ href = href.substring(href.indexOf(",") + 1);
+ this.sendAsyncMessage("addVCard", href);
+ }
+ event.preventDefault();
+ }
+ }
+}
diff --git a/comm/mail/actors/VCardParent.jsm b/comm/mail/actors/VCardParent.jsm
new file mode 100644
index 0000000000..71454a4a93
--- /dev/null
+++ b/comm/mail/actors/VCardParent.jsm
@@ -0,0 +1,17 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["VCardParent"];
+
+class VCardParent extends JSWindowActorParent {
+ receiveMessage({ data, target }) {
+ target.browsingContext.topChromeWindow.toAddressBook({
+ action: "create",
+ vCard: decodeURIComponent(data),
+ });
+ }
+}
diff --git a/comm/mail/actors/moz.build b/comm/mail/actors/moz.build
new file mode 100644
index 0000000000..da87ba7f0b
--- /dev/null
+++ b/comm/mail/actors/moz.build
@@ -0,0 +1,24 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXTRA_JS_MODULES += [
+ "../../../browser/modules/FaviconLoader.jsm",
+]
+
+FINAL_TARGET_FILES.actors += [
+ "../../../browser/actors/ContextMenuChild.sys.mjs",
+ "../../../browser/actors/LinkHandlerChild.sys.mjs",
+ "ChatActionChild.sys.mjs",
+ "ChatActionParent.sys.mjs",
+ "ContextMenuParent.sys.mjs",
+ "LinkClickHandlerChild.jsm",
+ "LinkClickHandlerParent.jsm",
+ "LinkHandlerParent.sys.mjs",
+ "MailLinkChild.jsm",
+ "MailLinkParent.jsm",
+ "PromptParent.jsm",
+ "VCardChild.jsm",
+ "VCardParent.jsm",
+]