diff --git a/README.md b/README.md
index 8be4787f1882eec74bbaf1619037b68b2b68cced..580cee758df54c1e02aa296af8f03e2fe36499ef 100644
--- a/README.md
+++ b/README.md
@@ -20,9 +20,7 @@ in such a way that the original link is accessible.
 * Since only the *display* is changed, links that have the URL as the
   link text will now have link text that differs from the link target.
   Thunderbird picks up on this when you click a link and asks if you
-  want to visit the original link or the safe link. Due to the way the
-  tooltip is constructed, visiting the original link will most likely
-  fail. This is not by design and may change in the future.
+  want to visit the original link or the safe link.
 
 * When composing a message the original links are restored. This
   process could potentially change text that isn't meant to be
diff --git a/extension/common.js b/extension/common.js
index 5b431a51069965cde5005841477edbe8109606c5..32f296f6c6e89933f2a48d2d379b33ba638d0e55 100644
--- a/extension/common.js
+++ b/extension/common.js
@@ -22,11 +22,33 @@
 // Shared code
 
 
+/**
+ * Regexp that matches safe links. The original URL must be collected
+ * in match group 1.
+ */
 const safelinksRegexp = new RegExp(
     'https?://[^.]+[.]safelinks[.]protection[.]outlook[.]com/[?]url=([^&]+)&.*',
     'gi'
 );
 
+
+/**
+ * The ID for the popup element that is added to the HTML document.
+ */
+const safelinksPopupId = 'safelinks-cleaner-thunderbird-popup';
+
+
+/**
+ * The class that is added to the popup when visible.
+ */
+const safelinksPopupVisibleClass = 'safelinks-cleaner-thunderbird-popup-visible';
+
+
+/**
+ * Return the original URL for a safe link.
+ * @param {string} link - The safe link.
+ * @returns {string} The original link or the safe link if there was an error.
+ */
 function untangleLink(link) {
     return link.replaceAll(
 	safelinksRegexp, (match, url) => {
@@ -40,10 +62,22 @@ function untangleLink(link) {
 	});
 }
 
+
+/**
+ * Check if a link is a safe link.
+ * @param {string} link - The URL to check.
+ * @returns {boolean} Returns true if the link is a safe link.
+ */
 function isTangledLink(link) {
     return link.match(safelinksRegexp);
 }
 
+
+/**
+ * Return the text nodes under a DOM element.
+ * @param {Element} elem - The element to return text nodes for.
+ * @returns {Element[]} The text elements under elem.
+ */
 function getTextNodes(elem) {
     var result = [];
     if (elem) {
diff --git a/extension/display.js b/extension/display.js
index 09c4b1d1ebe4d20efbf541612b2f0797c665643f..9ecc2593c41301e748f21af10f9c380c2859bd57 100644
--- a/extension/display.js
+++ b/extension/display.js
@@ -22,19 +22,140 @@
 // Display script
 
 
+let currentPopupTarget = null;
+let hidePopupTimeout = null;
+
+
+/**
+ * Return the popup div element, creating it if necessary.
+ * @returns {Element} The popup element.
+ */
+function getPopup() {
+    let popup = document.getElementById(safelinksPopupId);
+    if (!popup) {
+	popupElementLocked = false;
+	popup = document.createElement('div');
+	popup.id = safelinksPopupId;
+	popup.addEventListener('mouseenter', cancelHidePopup, {passive: true});
+	popup.addEventListener('mouseleave', scheduleHidePopup, {passive: true});
+	document.body.appendChild(popup);
+    }
+    return popup;
+}
+
+
+/**
+ * Cancel hiding the popup (if it has been scheduled) and set
+ * hidePopupTimeout to null.
+ */
+function cancelHidePopup() {
+    if (hidePopupTimeout) {
+	clearTimeout(hidePopupTimeout);
+	hidePopupTimeout = null;
+    }
+}
+
+
+/**
+ * Hide the current popup. If there is no popup, one will be created.
+ */
+function hidePopup() {
+    cancelHidePopup();
+    getPopup().classList.remove(safelinksPopupVisibleClass);
+    currentPopupTarget = undefined;
+}
+
+
+/**
+ * Schedule hiding the current popup.
+ */
+function scheduleHidePopup() {
+    if (!hidePopupTimeout) {
+	hidePopupTimeout = setTimeout(hidePopup, 100);
+    }
+}
+
+
+/**
+ * Get the absolute bounds of an element.
+ * @param {Element} elem - The element for which to return bounds.
+ * @returns {{top: number, left: number, right: number, bottom:
+ *   number}} The top, left, right, and bottom coordinates of the
+ *   element.
+ */
+function getAbsoluteBoundingRect(elem) {
+    let rect = elem.getBoundingClientRect();
+    let scrollLeft = window.scrollX;
+    let scrollTop = window.scrollY;
+    return {
+	top: rect.top + window.scrollY,
+	left: rect.left + window.scrollX,
+	bottom: rect.bottom + window.scrollY,
+	right: rect.right + window.scrollX,
+    }
+}
+
+/**
+ * Attempt to ensure that at least part of an element is visible. If
+ * the element's right-hand coordinate is off-screen, move it
+ * on-screen without moving the left-hand side off-screen. If the
+ * bottom of the element is off-screen, move it on-screen.
+ * @param {Element} elem - The element to show.
+ */
+function clampElementToDocument(elem) {
+    let elemBounds = getAbsoluteBoundingRect(elem);
+
+    if (elemBounds.bottom > document.documentElement.scrollHeight) {
+	elem.style.removeProperty('top');
+	elem.style.bottom = 0;
+    }
+
+    if (elemBounds.right > document.documentElement.scrollWidth) {
+	elem.style.removeProperty('left');
+	elem.style.right = 0;
+	if (getAbsoluteBoundingRect(elem).left < 0) {
+	    elem.style.left = 0;
+	}
+    }
+}
+
+/**
+ * Show the original URL of a link.
+ * @param {MouseEvent} event - The event triggering this handler.
+ */
+function showOriginalUrl(event) {
+    let popup = getPopup();
+    cancelHidePopup();
+    if (event.target != currentPopupTarget || !popup.classList.contains(safelinksPopupVisibleClass)) {
+	currentPopupTarget = event.target;
+	popup.textContent = untangleLink(event.target.href);
+	popup.style.removeProperty('bottom');
+	popup.style.removeProperty('right');
+	popup.style.left = event.clientX;
+	popup.style.top = event.clientY;
+	popup.classList.add(safelinksPopupVisibleClass);
+	//clampElementToDocument(popup);
+    }
+}
+
+/**
+ * Add event handlers to a link so it will show the original url.
+ * @param {Element} link - The link to add the popup to.
+ */
+function addLinkPopup(link) {
+    link.addEventListener('mouseenter', showOriginalUrl, {passive: true});
+    link.addEventListener('mouseleave', scheduleHidePopup, {passive: true});
+}
+
+
 for (const link of document.links) {
-    // Mangle the link text
+    // Untangle link text
     for (const node of getTextNodes(link)) {
 	node.textContent = untangleLink(node.textContent);
     }
 
-    // Generate the tooltip and set link class
+    // Create popup event handlers
     if (isTangledLink(link.href)) {
-	let tooltiptext = untangleLink(link.href);
-	let tooltip = document.createElement('span');
-	tooltip.classList.add('liu_safelinks_tooltip');
-	tooltip.textContent = tooltiptext;
-	link.classList.add('liu_safelinks_link');
-	link.prepend(tooltip);
+	addLinkPopup(link);
     }
 }
diff --git a/extension/manifest.json b/extension/manifest.json
index 208029ae1f767c6eeb3f26cb773d02b001164332..6991c2ddedeb82224799dafeffdd8761a286ba1e 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -2,7 +2,7 @@
     "manifest_version": 2,
     "name": "Microsoft ATP Safe Links Cleaner",
     "description": "__MSG_extensionDescription__",
-    "version": "1.1",
+    "version": "1.2",
     "author": "David Byers",
     "homepage_url": "https://safelinks.gitlab-pages.liu.se/safelinks-cleaner-thunderbird/",
     "default_locale": "en",
diff --git a/extension/style.css b/extension/style.css
index 169865b4f8977b689d0d752f02bf3741cc776f40..95f271cbd0983e90529959308997a5bc6a2ef07a 100644
--- a/extension/style.css
+++ b/extension/style.css
@@ -21,16 +21,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 */
 
-a:hover .liu_safelinks_tooltip {
-    display: block;
-}
 
-.liu_safelinks_tooltip {
+#safelinks-cleaner-thunderbird-popup {
     display: none;
     background: #fffff8;
     color: black;
     padding: 3px 3px 4px 3px;
-    position: absolute;
+    position: fixed;
     z-index: 1000;
     border: 1px solid black;
     -webkit-box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5);
@@ -38,3 +35,7 @@ a:hover .liu_safelinks_tooltip {
     box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5);
     font: 14px sans-serif;
 }
+
+#safelinks-cleaner-thunderbird-popup.safelinks-cleaner-thunderbird-popup-visible {
+    display: block;
+}