diff options
Diffstat (limited to 'comm/mail/actors/LinkClickHandlerChild.jsm')
-rw-r--r-- | comm/mail/actors/LinkClickHandlerChild.jsm | 178 |
1 files changed, 178 insertions, 0 deletions
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); + } + } +} |