fphilipe / PageExtender.app

Safari extension that injects custom CSS and JS files based on page host.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Possible to specify scripts for all domains?

gingerbeardman opened this issue · comments

As title.

Found it.

When visiting a page, e.g. https://en.wikipedia.org/wiki/Apple, the Safari extension will inject the following files into the page if they are present in the respective folders selected above:

∙ default.css ∙ default.js
∙ org.css ∙ org.js
∙ wikipedia.org.css ∙ wikipedia.org.js
∙ en.wikipedia.org.css ∙ en.wikipedia.org.js

Note that JS files are injected once the DOM is ready, i.e. once the DOMContentLoaded event has fired.

Curious, mind sharing what you're doing on all domains? 😊

Sure!

I'm replacing a bunch of legacy Safari Extensions — that injected simple scripts to every page with no user interface or options — which stopped working in Safari 12 (High Sierra, Mojave, etc)

// Remove overflow:hidden
// for sites that block scroll on popup
document.body.style.overflow = 'initial !important';

// Show Password on Double Click
function handleDblClick(e){
  var pwd = e.target;
  var type = pwd.getAttribute('type');
  pwd.setAttribute('type', type === 'password' ? 'text': 'password');
}
var passwords = document.querySelectorAll("input[type=password]");
for (var i = 0; i < passwords.length; i++){
  passwords[i].addEventListener('dblclick', handleDblClick, true);
}

// NavUpKey, by canisbos
// cmd+alt+up to go up the URL path
String.prototype.reverse = function () {
    var rs = '';
    for (var i = this.length - 1; i >= 0; i--)
        rs += this[i];
    return rs;
}
window.addEventListener('keydown', function (e) {
    if (e.which === 38 && (e.shiftKey * 1 + e.ctrlKey * 2 + e.altKey * 4 + e.metaKey * 8) === 12) {
        var l = window.location;
        var rp = l.pathname.reverse();
        var rpsi = rp.search(/.\//);
        if (rpsi > -1) {
            l.assign(l.protocol + '//' + l.host + rp.slice(rpsi+1).reverse());
        }
    }
}, true);

// Smart Right Click, by Mattijs
// allows right clicking of things that are blocked by other elements
document.addEventListener("mousedown", function(event) {
    if (event.which == 3) {
        event.stopPropagation();
    }
}, true);
document.addEventListener("contextmenu", function(event) {

    // show browser default right click only
    event.stopPropagation();

    // not a trusted context menu event
    if (!event.isTrusted || event.constructor.name != "MouseEvent") {
        return false;
    }

    // check if someone would right click this element for a specific reason
    function isElementIntention(intention, element) {
        var tag = element.nodeName.toLowerCase();

        // checks
        switch(intention) {
        case "ignore":
            // not an ignored tag
            if (!["body", "html"].includes(tag)) {
                return false;
            }
            break;
        case "link":
            // not within an "a" tag with a href
            if (!element.closest("a[href]")) {
                return false;
            }
            break;
        case "text":
            // hasn't made a selection
            if (window.getSelection().toString().length < 1) {
                return false;
            }
            break;
        case "media":
            // not a media tag
            if (!["img", "video"].includes(tag)) {
                return false;
            }
            switch(tag) {
            case "video":
                // doesn't have a source
                if (element.currentSrc.length < 1) {
                    return false;
                }
                break;
            case "img":
                // doesn't have a source
                if (element.currentSrc.length < 1) {
                    return false;
                }
                // didn't load
                if (element.naturalWidth === 0) {
                    return false;
                }
                break;
            }
            // is not visible
            if (getComputedStyle(element).opacity <= 0.1 || getComputedStyle(element).visibility == "hidden") {
                return false;
            }
            break;
        }

        // validated
        return true;
    };

    // get the most likely reason someone would right click this element
    function getElementIntention(element) {
        var intention = null;
        if (isElementIntention("ignore", element)) {
            intention = "ignore";
        } else if (isElementIntention("text", element)) {
            intention = "text";
        } else if (isElementIntention("link", element)) {
            intention = "link";
        }
        if ((!intention || intention == "link") && isElementIntention("media", element)) {
            intention = "media";
        }
        return intention;
    };
    
    // if unknown intention - check a bit deeper
    var eventTargetIntention = getElementIntention(event.target);
    if (!eventTargetIntention || eventTargetIntention == "link") {

        // check if pointer intersects with any more likely targets
        var timer = new Date();
        var intendedTarget = null;
        var potentialTargets = document.querySelectorAll("img, video");
        for (var i = potentialTargets.length; i--;) {
            var potentialTarget = potentialTargets[i];
            var bounds = potentialTarget.getBoundingClientRect();
            bounds = { l: bounds.left, t: bounds.top, r: bounds.left + bounds.width, b: bounds.top + bounds.height };

            // check if it's under the pointer - and not hidden
            if (event.clientX >= bounds.l && event.clientX <= bounds.r && event.clientY >= bounds.t && event.clientY <= bounds.b && isElementIntention("media", potentialTarget) && (eventTargetIntention != "link" || event.target.closest("a[href]").contains(potentialTarget))) {
                intendedTarget = potentialTarget;
                break;
            }

            // stop if it's taking too long
            if ((new Date() - timer) > 10) {
                break;
            }
        }

        // if we found the real intended target
        if (intendedTarget) {

            // enable pointer events for the intended element
            var disabled = {
                elements: [intendedTarget],
                pointerEvents: [{
                    value: intendedTarget.style.getPropertyValue("pointer-events"),
                    priority: intendedTarget.style.getPropertyPriority("pointer-events")
                }]
            }
            intendedTarget.style.setProperty("pointer-events", "all", "important");

            // go through disabling everything above the intended element so that the right click hits it
            var timer = new Date();
            var beneath = false;
            for (var i = 0; i < 10; i++) {
                var currentTarget = (i == 0) ? event.target : document.elementFromPoint(event.clientX, event.clientY);

                // check if the loop should continue
                if (!currentTarget || disabled.elements.indexOf(currentTarget) !== -1 && currentTarget !== intendedTarget) {
                    break;

                // found intended target
                } else if (currentTarget === intendedTarget || isElementIntention("media", currentTarget)) {
                    if (disabled.elements.length > 1) beneath = true;
                    break;
                }

                // push the element and style
                disabled.elements.push(currentTarget);
                disabled.pointerEvents.push({
                    value: currentTarget.style.getPropertyValue("pointer-events"),
                    priority: currentTarget.style.getPropertyPriority("pointer-events")
                });

                // add "pointer-events: none", to get to the underlying element
                currentTarget.style.setProperty("pointer-events", "none", "important"); 

                // stop if it's taking too long
                if ((new Date() - timer) > 20) {
                    break;
                }
            }
            
            // revert the changes
            function revert(disabled) {
                for (var i = disabled.pointerEvents.length; d = disabled.pointerEvents[--i];) {
                    var element = disabled.elements[i];
                    element.style.setProperty("pointer-events", (d.value ? d.value : ""), d.priority);
                    if (!element.getAttribute("style")) {
                        element.removeAttribute("style");
                    }
                }
            };

            // change right click target
            if (beneath) {
                setTimeout(revert, 10, disabled);

                // notify my other extensions to check the right click event again
                intendedTarget.dispatchEvent(new MouseEvent(event.type, event));
            
            // keep it as is
            } else {
                revert(disabled);
            }

        // check parent window instead and somehow set "pointer-events: none" to this iframe
        } else if (window !== window.top) {
            // ..

        }
    }
}, true);

@gingerbeardman This is super cool! Thanks for sharing

@gingerbeardman Also, sorry to bother you—how did you install these? I assume you put them in a default.js file? Also, where did you find these, did you write them yourself? Thanks!

@sn0wyfall yes default.js

credits are next to each snippet. Canisbos, Mattijs you'd have to Google to find the source. Probably stack overflow or an old open source safari extension.

The top two short pieces I believe are mine. It's been too long for me to really remember.

My bad, I can't read and I just realized what you the quote meant after reading it.

Thanks for the reply! I was hoping to find a way to replicate StopTheMadness, but I guess that's being too greedy :D. Thanks for posting your script! It'll be really useful.

You could replicate that, but you'd either have to steal the JS/CSS (not good) or rewrite your own versions (good luck).

Yeah, you're right—I think I'll just stick with these scripts for now. Not such a huge deal anyway! It does most of what I would have wanted it to do.

@gingerbeardman Sorry for bothering you. I've been trying to get those JavaScript scripts to work, but have not been successful yet. I tried going here and double clicking on the password, but it didn't show up. I also tried going on an Instagram page and scrolling, but it didn't let me after the log in popup appeared. I tried going on this site and tried to right click on the image, but again no go. It seems like my JS isn't working.

But that's strange, since my CSS works and I put the JS all in a default.js file, copied exactly as you have it here. Do you have any examples of where it works? I would love to test it out.

@sn0wyfall a few things:

  1. I just checked and I no longer use all of the JS above, including the right click image script.
  2. Perhaps Safari has changed JS permissions, the password snippet no longer works for me, sorry.
  3. the overflow script only works on pages that set it as part of the load, if the page sets it dynamically (like Instagram) then it will not work and would need to be rewritten to work on dynamic pages.

Ideally, there would be a repo of small snippets with test URLs, but we're not there yet.

Got it! Thanks a ton for your insight :)