diff --git a/edge/PREPARE b/edge/BUILD similarity index 60% rename from edge/PREPARE rename to edge/BUILD index 575ff42b096b6fc1eae4d37210413332be7dba79..cbfeb4d021de2b45c9b9afa886f1cad806dd5dc8 100644 --- a/edge/PREPARE +++ b/edge/BUILD @@ -1,3 +1,8 @@ +build --source firefox \ + --target edge \ + --override "edge/manifest.override.json" \ + --ext zip + for size in 16 32 128 ; do convert -density 256x256 -background transparent "$SHAREDDIR/icon.svg" \ -resize ${size}x${size} "$targetdir/icon${size}x${size}.png" diff --git a/edge/TARGET b/edge/TARGET deleted file mode 100644 index dbfb8f931b33282ce7d78b6ea117eba71390087d..0000000000000000000000000000000000000000 --- a/edge/TARGET +++ /dev/null @@ -1 +0,0 @@ -firefox diff --git a/firefox/content.js b/firefox/content.js index b462aa307956c6d8a15ab3911c9651352ad34e14..1fff7870ae0480294adfa6810ec9c1ec740130ef 100644 --- a/firefox/content.js +++ b/firefox/content.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,14 +22,5 @@ // Content script -/** - * 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', withoutMutationObserver(showOriginalUrl), {passive: true}); - link.addEventListener('mouseleave', scheduleHidePopup, {passive: true}); -} - mutationHandler(); enableMutationObserver(); diff --git a/firefox/manifest.merge.json b/firefox/manifest.merge.json index 7867bd944d934f003152691553c325bd2d988197..5e26fd585e170d49ef0fbd0e3934cb21a0ecfd64 100644 --- a/firefox/manifest.merge.json +++ b/firefox/manifest.merge.json @@ -14,9 +14,9 @@ "style.css" ], "js": [ + "mutation.js", "links.js", "popup.js", - "mutation.js", "content.js" ] } diff --git a/scripts/build.sh b/scripts/build.sh index 682e7499574f0fb33afe2b4fd23bcb8863e3133c..1a33e4bbc281049e507ddb3a4f3900338b53c1ed 100644 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -2,13 +2,9 @@ set -eu -TARGETS=(thunderbird firefox edge) -BASEDIR="$( cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 && pwd )" - - usage() { cat <<EOF -Usage: $0 [--beta] [--debug] [--version VERSION] +Usage: build.sh [--beta] [--debug] [--version VERSION] --beta Beta build (can also set CI_COMMIT_BRANCH to beta) --debug Debug build (can also set DEBUG_BUILD) @@ -17,120 +13,117 @@ EOF exit 1 } -beta="" -debug="${DEBUG_BUILD:-}" -version="${BUILD_VERSION:-}" - -branch="${CI_COMMIT_BRANCH:-}" -if [ "$branch" = "beta" ] ; then - beta=yes -fi - - -while [ $# -gt 0 ] ; do - case "$1" in - --version) - version="$2" - shift - ;; - --beta) - beta=yes - ;; - --debug) - debug=yes - ;; - --) - shift - break - ;; - -h|--help) - usage - ;; - -*) - usage - ;; - *) - break - ;; - esac - shift -done - -[ $# -eq 0 ] || usage -[ "$version" ] || usage +fatal() { + echo "$*" >&2 + exit 1 +} +parse_args() { + beta="" + debug="${DEBUG_BUILD:-}" + version="${BUILD_VERSION:-}" -BUILDDIR="$BASEDIR/build" -SHAREDDIR="$BASEDIR/shared" + branch="${CI_COMMIT_BRANCH:-}" + if [ "$branch" = "beta" ] ; then + beta=yes + fi -if [ -x "$BASEDIR/node_modules/.bin/web-ext" ] ; then - WEB_EXT="$BASEDIR/node_modules/.bin/web-ext" -else - WEB_EXT="web-ext" -fi + while [ $# -gt 0 ] ; do + case "$1" in + --version) + version="$2" + shift + ;; + --beta) + beta=yes + ;; + --debug) + debug=yes + ;; + --) + shift + break + ;; + -h|--help) + usage + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift + done + + [ $# -eq 0 ] || usage + [ "$version" ] || usage +} -if [ "$beta" ] ; then - version_name="$version beta" -else - version_name="$version" -fi +setup_globals() { + TARGETS=(thunderbird firefox edge) + BASEDIR="$( cd "$(dirname "${BASH_SOURCE[0]}")/.." >/dev/null 2>&1 && pwd )" -declare -a merge -declare -a override + BUILDDIR="$BASEDIR/build" + SHAREDDIR="$BASEDIR/shared" -for target in "${TARGETS[@]}" ; do - targetdir="$BUILDDIR/$target" - sourcedir="$BASEDIR/$target" - xpifile="safelinks-cleaner-$target.xpi" - outputfile="$BUILDDIR/$xpifile" - origdir= - - echo -n "[+] building target '$target'" - if [ -f "$sourcedir/TARGET" ] ; then - echo " (alias for $(cat $sourcedir/TARGET))" - origdir="$sourcedir" - sourcedir="$BASEDIR/$(cat $sourcedir/TARGET)" + if [ -x "$BASEDIR/node_modules/.bin/web-ext" ] ; then + WEB_EXT="$BASEDIR/node_modules/.bin/web-ext" else - echo + WEB_EXT="web-ext" fi - [ -d "$targetdir" ] && rm -r "$targetdir" - mkdir -p "$targetdir" - - [ -f "$origdir/PREPARE" ] && source "$origdir/PREPARE" + if [ "$beta" ] ; then + version_name="$version beta" + else + version_name="$version" + fi +} +copy_source() { cp -r "$SHAREDDIR"/* "$targetdir" cp -r "$sourcedir"/* "$targetdir" - rm -f "$targetdir"/*.merge.json \ - "$targetdir"/*.override.json \ - "$targetdir"/PREPARE \ - "$targetdir"/TARGET \ - "$targetdir"/*~ \ - "$targetdir"/#* +} - if [ -z "${DEBUG:-}" ] ; then +clean_debug() { + if [ -z "${debug:-}" ] ; then for file in "$targetdir"/*.js ; do sed -i -e '/\/\/ DEBUG$/d' "$file" done fi +} - merge=("$SHAREDDIR/manifest.merge.json") - [ -f "$sourcedir/manifest.merge.json" ] && \ - merge+=("$sourcedir/manifest.merge.json") - [ "$origdir" -a -f "$origdir/manifest.merge.json" ] && \ - merge+=("$origdir/manifest.merge.json") - [ "$beta" -a -f "$sourcedir/manifest.beta.merge.json" ] && \ - merge+=("$sourcedir/manifest.beta.merge.json") - [ "$origdir" -a "$beta" -a -f "$origdir/manifest.beta.merge.json" ] && \ - merge+=("$origdir/manifest.beta.merge.json") +clean_target() { + rm -f "$targetdir"/*.merge.json \ + "$targetdir"/*.override.json \ + "$targetdir"/BUILD \ + "$targetdir"/*~ \ + "$targetdir"/#* +} - override=() - [ "$origdir" -a -f "$origdir/manifest.override.json" ] && \ - override+=("$origdir/manifest.override.json") - [ "$beta" -a -f "$sourcedir/manifest.beta.override.json" ] && \ - override+=("$sourcedir/manifest.beta.override.json") - [ "$origdir" -a "$beta" -a -f "$origdir/manifest.beta.override.json" ] && \ - override+=("$origdir/manifest.beta.override.json") +build_manifests() { + local merge=() + local override=() + local collect_merge + local collect_override + + while [ $# -gt 0 ] ; do + case "$1" in + --merge) + collect="merge" + ;; + --override) + collect="override" + ;; + *) + [ "$collect" = "merge" ] && merge+=("$1") + [ "$collect" = "override" ] && override+=("$1") + ;; + esac + shift + done + [ $# -eq 0 ] || fatal "internal error in call to build" python3 "$BASEDIR/scripts/makemanifest.py" \ --merge "${merge[@]}" --override "${override[@]}" \ @@ -141,5 +134,89 @@ for target in "${TARGETS[@]}" ; do sed -i -e "s|%VERSION_NAME%|$version_name|g" "$targetdir/manifest.json" sed -i -e "s|%PAGES_URL%|$CI_PAGES_URL|g" "$targetdir/manifest.json" sed -i -e "s|%PROJECT_URL%|$CI_PROJECT_URL|g" "$targetdir/manifest.json" +} + +build() { + local merge=() + local tmp_merge=() + local override=() + local tmp_override=() + local quiet= + + while [ $# -gt 0 ] ; do + case "$1" in + --target) + target="$2" + targetdir="$BUILDDIR/$2" + [ "$sourcedir" ] || sourcedir="$targetdir" + shift + ;; + --source) + source="$2" + sourcedir="$BASEDIR/$2" + shift + ;; + --ext) + ext="$2" + shift + ;; + --merge) + tmp_merge+=("$2") + shift + ;; + --override) + tmp_override+=("$2") + shift + ;; + --quiet) + quiet=yes + ;; + *) + fatal "internal error in call to build: $*" + ;; + esac + shift + done + [ $# -eq 0 ] || fatal "internal error in call to build" + + local defaultmerge + declare -a defaultmerge + + merge+=("$SHAREDDIR/manifest.merge.json") + [ -f "$sourcedir/manifest.merge.json" ] && \ + merge+=("$sourcedir/manifest.merge.json") + [ "$beta" -a -f "$sourcedir/manifest.beta.merge.json" ] && \ + merge+=("$sourcedir/manifest.beta.merge.json") + [ "$beta" -a -f "$sourcedir/manifest.beta.override.json" ] && \ + merge+=("$sourcedir/manifest.beta.override.json") + + merge+=("${tmp_merge[@]}") + override+=("${tmp_override[@]}") + + xpifile="safelinks-cleaner-$target.$ext" + outputfile="$BUILDDIR/$xpifile" + + [ "$quiet" ] || echo "[+] building target '$target'" + + [ -d "$targetdir" ] && rm -r "$targetdir" + mkdir -p "$targetdir" + + copy_source + clean_target + clean_debug + build_manifests --merge "${merge[@]}" --override "${override[@]}" +} + +parse_args "$@" +setup_globals + +[ "$debug" ] && echo "[+] debugging statements enabled" + +for target in "${TARGETS[@]}" ; do + if [ -f "$target/BUILD" ] ; then + . "$target/BUILD" + else + build --source "$target" --target "$target" --ext "xpi" + fi done diff --git a/shared/links.js b/shared/links.js index ded7ee5254efe93307c7ea79d96abc1c2b1d7e2b..0a9fe5afbf67ec9d8479aa2731841aa64c2ddfa0 100644 --- a/shared/links.js +++ b/shared/links.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -98,6 +98,16 @@ function getTextNodes(elem) { } +/** + * 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', withoutMutationObserver(showOriginalUrl), {passive: true}); + link.addEventListener('mouseleave', scheduleHidePopup, {passive: true}); +} + + /** * Fix all the links in the document. * @param {Element} root - DOM element in which to fix links. diff --git a/shared/menu.js b/shared/menu.js index a4f766de6522e08b37602bd85f4dec472c40c9df..6dd91cdca89c76ba80dff9e9392e8e3e65810ba6 100644 --- a/shared/menu.js +++ b/shared/menu.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/firefox/mutation.js b/shared/mutation.js similarity index 99% rename from firefox/mutation.js rename to shared/mutation.js index 708b976a7556dbd77f12e83b79896f4a450dde13..af84bc644e8950f4a91a47b73e68d8c94d6e91e1 100644 --- a/firefox/mutation.js +++ b/shared/mutation.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/shared/popup.js b/shared/popup.js index 052db2de08fc881342da09a5250f4b2fdc8b2026..cc1db79ae30bfa4655ce27ce9a8f681847976442 100644 --- a/shared/popup.js +++ b/shared/popup.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,6 +22,13 @@ // Popups +// Constants controlling popup placement + +const maxDistanceFromMouse = 30; // Max distance from mouse to popup +const belowPreferenceWeight = 1.5; // How much do we prefer below placement +const belowPreferenceMargin = 30; // How much closer does top need to be to even care + + let currentPopupTarget = null; let hidePopupTimeout = null; @@ -92,31 +99,24 @@ function getAbsoluteBoundingRect(elem) { left: rect.left + window.scrollX, bottom: rect.bottom + window.scrollY, right: rect.right + window.scrollX, + height: rect.height, + width: rect.width } } /** - * 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. + * Get the viewport bounding rectangle relative the document. + * @returns {object} The bounding rectagle. */ -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; - } +function getViewportBoundingRect() { + return { + top: window.scrollY, + left: window.scrollX, + bottom: window.scrollY + window.innerHeight, + right: window.scrollX + window.innerWidth, + height: window.innerHeight, + width: window.innerWidth } } @@ -134,9 +134,79 @@ function showOriginalUrl(event) { popup.textContent = untangleLink(event.target.href); popup.style.removeProperty('bottom'); popup.style.removeProperty('right'); - popup.style.left = event.clientX + 'px'; - popup.style.top = event.clientY + 'px'; + + // Get the bounds of the target and viewport + let targetBounds = getAbsoluteBoundingRect(event.target); + let viewportBounds = getViewportBoundingRect(); + + console.log('targetBounds', targetBounds); // DEBUG + console.log('viewportBounds', viewportBounds); // DEBUG + + // Set up the popup and get its initial bounds + popup.style.left = Math.max(targetBounds.left, window.scrollX) + 'px'; + popup.style.top = '-65535px'; popup.classList.add(safelinksPopupVisibleClass); - clampElementToDocument(popup); + let popupBounds = getAbsoluteBoundingRect(popup); + + // Determine the initial position of the popup + let mouseY = event.clientY + window.scrollY; + let distanceToTop = Math.abs(mouseY - targetBounds.top); + let distanceToBottom = Math.abs(mouseY - targetBounds.bottom); + let topIsCloser = (distanceToBottom > belowPreferenceMargin && + distanceToTop * belowPreferenceWeight < distanceToBottom); + let aboveWouldBeVisible = (targetBounds.top - popupBounds.height) >= viewportBounds.top; + let belowWouldBeVisible = (targetBounds.bottom + popupBounds.height) <= viewportBounds.bottom; + + console.log({distanceToTop: distanceToTop, // DEBUG + distanceToBottom: distanceToBottom, // DEBUG + topIsCloser: topIsCloser, // DEBUG + aboveWouldBeVisible: aboveWouldBeVisible, // DEBUG + belowWouldBeVisible: belowWouldBeVisible}); // DEBUG + + if ((topIsCloser && aboveWouldBeVisible) || !belowWouldBeVisible) { + console.log('initial position: top'); // DEBUG + popup.style.top = (targetBounds.top - popupBounds.height) + 'px'; + } + else if ((!topIsCloser && belowWouldBeVisible) || !aboveWouldBeVisible) { + console.log('initial position: bottom'); // DEBUG + popup.style.top = targetBounds.bottom + 'px'; + } + else { + console.log('initial position: not good'); // DEBUG + popup.style.top = targetBounds.bottom + 'px'; + } + + // Get the updated bounds and proceed to adjustments + popupBounds = getAbsoluteBoundingRect(popup); + + // If the popup is really far from the mouse, move it closer + if (popupBounds.top - mouseY > maxDistanceFromMouse) { + console.log('moving upwards to mouse'); // DEBUG + popup.style.top = (mouseY + 2) + 'px'; + popupBounds = getAbsoluteBoundingRect(popup); + } + else if (mouseY - popupBounds.bottom > maxDistanceFromMouse) { + console.log('moving downwards to mouse'); // DEBUG + popup.style.top = (mouseY - popupBounds.height - 2) + 'px'; + popupBounds = getAbsoluteBoundingRect(popup); + } + + // Clamp to bottom of viewport rect + if (popupBounds.bottom > viewportBounds.bottom) { + console.log('clamping to bottom'); // DEBUG + popup.style.top = (viewportBounds.bottom - popupBounds.height) + 'px'; + } + + // Clamp to top of viewport rect + if (popupBounds.top < viewportBounds.top) { + console.log('clamping to top'); // DEBUG + popup.style.top = viewportBounds.top + 'px'; + } + + // Clamp to left of viewport rect + if (popupBounds.left < viewportBounds.left) { + console.log('clamping to left'); // DEBUG + popup.style.left = viewportBounds.left; + 'px'; + } } } diff --git a/shared/style.css b/shared/style.css index 3ba19a964c6ebb0a104d7b9924d15eec36b38297..421635bdaaa0d7816ed21ea1810827a9ac1f9395 100644 --- a/shared/style.css +++ b/shared/style.css @@ -1,5 +1,5 @@ /* -ATP Safe Links Cleaner +Safe Links Cleaner Copyright 2021 David Byers <david.byers@liu.se> Permission is hereby granted, free of charge, to any person obtaining a copy @@ -24,16 +24,13 @@ SOFTWARE. #safelinks-cleaner-thunderbird-popup { display: none; - background: #fffff8; - color: black; + background: #555555; + color: #ffffff; padding: 3px 3px 4px 3px; - position: fixed; + position: absolute; z-index: 1000; border: 1px solid black; - -webkit-box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5); - -moz-box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5); - box-shadow: 0px 0px 6px 1px rgba(0,0,0,0.5); - font: 14px sans-serif; + font: 12px sans-serif; } #safelinks-cleaner-thunderbird-popup.safelinks-cleaner-thunderbird-popup-visible { diff --git a/thunderbird/background.js b/thunderbird/background.js index f6b76b6fac42bc8c18b9ca9c849f772886b87308..28df119348754275ab22ec40920ad352d2e9eefc 100644 --- a/thunderbird/background.js +++ b/thunderbird/background.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -27,6 +27,7 @@ browser.composeScripts.register({ {file: "/style.css"} ], js: [ + {file: "mutation.js"}, {file: "links.js"}, {file: "popup.js"}, {file: "compose.js"} @@ -35,11 +36,12 @@ browser.composeScripts.register({ browser.messageDisplayScripts.register({ css: [ - {file: "/style.css"} + {file: "style.css"} ], js: [ - {file: "/links.js"}, - {file: "/popup.js"}, - {file: "/display.js"} + {file: "mutation.js"}, + {file: "links.js"}, + {file: "popup.js"}, + {file: "display.js"}, ], }); diff --git a/thunderbird/compose.js b/thunderbird/compose.js index bf7a1c9e47320edb046585fc2b866721fe365b3a..1e2c427740dbe3a1c1e76041551dda69e71b6448 100644 --- a/thunderbird/compose.js +++ b/thunderbird/compose.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/thunderbird/display.js b/thunderbird/display.js index 292ba3b85ac875fdfed10a6678c80b371f4539c9..ebdd02f1433ac3117192a6d0efeaa1316c3fae9f 100644 --- a/thunderbird/display.js +++ b/thunderbird/display.js @@ -1,4 +1,4 @@ -// ATP Safe Links Cleaner +// Safe Links Cleaner // Copyright 2021 David Byers <david.byers@liu.se> // // Permission is hereby granted, free of charge, to any person obtaining a copy @@ -22,13 +22,4 @@ // Display script -/** - * 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}); -} - fixAllTheLinks(document.body);