diff --git a/chrome/content/zotero/actors/ExternalLinkHandlerChild.jsm b/chrome/content/zotero/actors/ExternalLinkHandlerChild.jsm index 711abc07ed..7e1cd4e586 100644 --- a/chrome/content/zotero/actors/ExternalLinkHandlerChild.jsm +++ b/chrome/content/zotero/actors/ExternalLinkHandlerChild.jsm @@ -7,18 +7,62 @@ class ExternalLinkHandlerChild extends JSWindowActorChild { case "click": { let { button, target } = event; if (button !== 0) { - break; + return; } - if ((target.localName === 'a' || target.localName === 'area') && target.href - || target.localName === 'label' && target.classList.contains('text-link')) { - event.stopPropagation(); - event.preventDefault(); - await this._sendLaunchURL(target.href || target.getAttribute('href')); + + let href; + if (target.localName === 'a' || target.localName === 'area') { + href = target.href; } + else if (target.localName === 'label' && target.classList.contains('text-link')) { + href = target.getAttribute('href'); + } + + if (!href || this._shouldOpenInternally(href)) { + return; + } + + event.stopPropagation(); + event.preventDefault(); + await this._sendLaunchURL(href); break; } } } + + _shouldOpenInternally(href) { + let hrefURL; + try { + hrefURL = new URL(href); + } + catch (e) { + // Not a valid URL: open externally + return false; + } + let currentURL = this.contentWindow.location; + + // eslint-disable-next-line no-script-url + if (hrefURL.protocol === 'javascript:') { + // Link executes a script: open internally + return true; + } + + if (hrefURL.origin + hrefURL.pathname + hrefURL.search === currentURL.origin + currentURL.pathname + currentURL.search + && hrefURL.hash) { + // Link points to the same page with a hash: open internally + return true; + } + + if (hrefURL.origin === 'https://www.zotero.org' && /^\/styles\/[^/?#]+$/.test(hrefURL.pathname)) { + // Links points directly to CSL in the repo: open internally + return true; + } + + // Everything else: open externally + // This might include links that Zotero.launchURL() is just going to reject, + // like chrome:// URLs, but we'll just let it print that error + return false; + } async _sendLaunchURL(url) { await this.sendAsyncMessage("launchURL", url);