diff options
Diffstat (limited to 'comm/mail/base/content/contentAreaClick.js')
-rw-r--r-- | comm/mail/base/content/contentAreaClick.js | 206 |
1 files changed, 206 insertions, 0 deletions
diff --git a/comm/mail/base/content/contentAreaClick.js b/comm/mail/base/content/contentAreaClick.js new file mode 100644 index 0000000000..647c4e7551 --- /dev/null +++ b/comm/mail/base/content/contentAreaClick.js @@ -0,0 +1,206 @@ +/** + * 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/. */ + +/* import-globals-from ../../../../toolkit/content/contentAreaUtils.js */ +/* import-globals-from utilityOverlay.js */ + +/* globals getMessagePaneBrowser */ // From aboutMessage.js + +var { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); +XPCOMUtils.defineLazyModuleGetters(this, { + PhishingDetector: "resource:///modules/PhishingDetector.jsm", +}); +XPCOMUtils.defineLazyPreferenceGetter( + this, + "alternativeAddonSearchUrl", + "extensions.alternativeAddonSearch.url" +); + +XPCOMUtils.defineLazyPreferenceGetter( + this, + "canonicalAddonServerUrl", + "extensions.canonicalAddonServer.url" +); +/** + * 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 [href, linkText] the url and the text for the link being clicked. + */ +function hRefForClickEvent(aEvent, aDontCheckInputElement) { + let target = + aEvent.type == "command" + ? document.commandDispatcher.focusedElement + : aEvent.target; + + if ( + HTMLImageElement.isInstance(target) && + target.hasAttribute("overflowing") + ) { + // Click on zoomed image. + return [null, null]; + } + + let href = null; + let linkText = null; + if ( + HTMLAnchorElement.isInstance(target) || + HTMLAreaElement.isInstance(target) || + HTMLLinkElement.isInstance(target) + ) { + if (target.hasAttribute("href")) { + href = target.href; + linkText = gatherTextUnder(target); + } + } else if ( + !aDontCheckInputElement && + (HTMLInputElement.isInstance(target) || + HTMLButtonElement.isInstance(target)) + ) { + if (target.form && target.form.action) { + href = target.form.action; + } + } else { + // We may be nested inside of a link node. + let linkNode = aEvent.target; + while (linkNode && !HTMLAnchorElement.isInstance(linkNode)) { + linkNode = linkNode.parentNode; + } + + if (linkNode) { + href = linkNode.href; + linkText = gatherTextUnder(linkNode); + } + } + return [href, linkText]; +} + +/** + * Check whether the click target's or its ancestor's href + * points to an anchor on the page. + * + * @param HTMLElement aTargetNode - the element node. + * @returns - true if link pointing to anchor. + */ +function isLinkToAnchorOnPage(aTargetNode) { + let url = aTargetNode.ownerDocument.URL; + if (!url.startsWith("http")) { + return false; + } + + let linkNode = aTargetNode; + while (linkNode && !HTMLAnchorElement.isInstance(linkNode)) { + linkNode = linkNode.parentNode; + } + + // It's not a link with an anchor. + if (!linkNode || !linkNode.href || !linkNode.hash) { + return false; + } + + // The link's href must match the document URL. + if (makeURI(linkNode.href).specIgnoringRef != makeURI(url).specIgnoringRef) { + return false; + } + + return true; +} + +// Called whenever the user clicks in the content area, +// should always return true for click to go through. +function contentAreaClick(aEvent) { + let target = aEvent.target; + if (target.localName == "browser") { + // This is a remote browser. Nothing useful can happen in this process. + return true; + } + + // If we've loaded a web page url, and the element's or its ancestor's href + // points to an anchor on the page, let the click go through. + // Otherwise fall through and open externally. + if (isLinkToAnchorOnPage(target)) { + return true; + } + + let [href, linkText] = hRefForClickEvent(aEvent); + + if (!href && !aEvent.button) { + // Is this an image that we might want to scale? + + if (HTMLImageElement.isInstance(target)) { + // Make sure it loaded successfully. No action if not or a broken link. + var req = target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST); + if (!req || req.imageStatus & Ci.imgIRequest.STATUS_ERROR) { + return false; + } + + // Is it an image? + if (target.localName == "img" && target.hasAttribute("overflowing")) { + if (target.hasAttribute("shrinktofit")) { + // Currently shrunk to fit, so unshrink it. + target.removeAttribute("shrinktofit"); + } else { + // User wants to shrink now. + target.setAttribute("shrinktofit", true); + } + + return false; + } + } + return true; + } + + if (!href || aEvent.button == 2) { + return true; + } + + // We want all about, http and https links in the message pane to be loaded + // externally in a browser, therefore we need to detect that here and redirect + // as necessary. + let uri = makeURI(href); + if ( + Cc["@mozilla.org/uriloader/external-protocol-service;1"] + .getService(Ci.nsIExternalProtocolService) + .isExposedProtocol(uri.scheme) && + !uri.schemeIs("http") && + !uri.schemeIs("https") + ) { + return true; + } + + // Add-on names in the Add-On Manager are links, but we don't want to do + // anything with them. + if (uri.schemeIs("addons")) { + return true; + } + + // Now we're here, we know this should be loaded in an external browser, so + // prevent the default action so we don't try and load it here. + aEvent.preventDefault(); + + // Let the phishing detector check the link. + let urlPhishCheckResult = PhishingDetector.warnOnSuspiciousLinkClick( + window, + href, + linkText + ); + if (urlPhishCheckResult === 1) { + return false; // Block request + } + + if (urlPhishCheckResult === 0) { + // Use linkText instead. + openLinkExternally(linkText); + return true; + } + + openLinkExternally(href); + return true; +} |