Skip to content
Snippets Groups Projects
display.js 4.81 KiB
Newer Older
  • Learn to ignore specific revisions
  • // Microsoft ATP Safe Links Cleaner
    // Copyright 2021 David Byers <david.byers@liu.se>
    //
    // Permission is hereby granted, free of charge, to any person obtaining a copy
    // of this software and associated documentation files (the "Software"), to deal
    // in the Software without restriction, including without limitation the rights
    // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    // copies of the Software, and to permit persons to whom the Software is
    // furnished to do so, subject to the following conditions:
    // 
    // The above copyright notice and this permission notice shall be included in all
    // copies or substantial portions of the Software.
    // 
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    // SOFTWARE.
    
    // 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) {
    
        for (const node of getTextNodes(link)) {
    	node.textContent = untangleLink(node.textContent);
        }
    
    
        if (isTangledLink(link.href)) {