luciopaiva / witchcraft

Inject Javascript and CSS right from your file system. Think GreaseMonkey for more advanced users.
https://luciopaiva.com/witchcraft
MIT License
254 stars 18 forks source link

Inject only once with multiple frames #23

Closed dariop closed 4 years ago

dariop commented 4 years ago

Hi, I'm trying to enhance an old web application that I use at work. This website has a lot of nested frames and iframes, some scripts that force reloads, so for every useful injection of JS/CSS, Witchcraft does 23 (!) unuseful injections! Is there a way to do injection only once per page? I'm trying to do it detecting in which frame the script is injected, but the overhead (and wasted RAM) is still huge.

Thank you!

Dario

luciopaiva commented 4 years ago

Hi @dariop,

Witchcraft injects itself into every frame by design. When registering as an extension, Witchcraft asks Chrome for all-frames access. Here's an excerpt from manifest.json:

 "content_scripts": [{
    "all_frames": true,

That's the only way to support scripts that require frame/iframe injection. The downside is, of course, if you don't want that feature, you'll need to deal with it.

Every time I face that same issue, I usually only want to inject Witchcraft into the topmost window. If that's your case, it can easily be accomplished by doing the check below:

function isTopmostWindow() {
    return window.self === window.top;
}

This function will only return true for the topmost window.

Don't worry about the CPU overhead. This check is a really simple operation, just a boolean comparison. Considering the web application loads 23 frames, Witchcraft loading itself into each frame is the least of your concerns 😄. Witchcraft is a lean extension, just check the code. It doesn't depend on any libraries and the code is really tiny, especially the part that is injected into every frame. It is so small that I can even paste the complete code here:

/*
   This is the part that gets injected in each frame of the page. It signals the Witchcraft background script that a new
   frame has been loaded and gets ready (by creating a listener) to receive and load any matching scripts.
 */

chrome.runtime.onMessage.addListener(({scriptType, scriptContents}) => {
    if (scriptType === "js") {
        Function(scriptContents)();
    } else if (scriptType === "css") {
        const style = document.createElement('style');
        style.type = 'text/css';
        style.appendChild(document.createTextNode(scriptContents));
        style.setAttribute("data-witchcraft", "");  // to make it easy finding the element if we want to
        document.head.appendChild(style);
    }
});

chrome.runtime.sendMessage(location.hostname);

So you shouldn't worry about memory usage either.

Well, that's it. I hope my answer made sense to you. Great question, anyway. I will add it to the FAQ.

I'm closing this ticket, but feel free to discuss it further if you want and please let me know if you need more help.

Thanks!

luciopaiva commented 4 years ago

Question added to the FAQ.

dariop commented 4 years ago

Hello @luciopaiva , thank you for your kind answer.

I was concerned about injection of my code, not yours :) Furthermore, I need to inject some data (~1MB), so I need to be accurate

Anyway, for some reasons regarding the web application I want to extend, I can't inject my code in the topmost window, so I solved in another way: I check if we are in the frame I care about, and in this case (and only in this case) I do some stuff.

if(window.name==='my_frame'){
    $(document).ready(function(){
        //load my data once
        if(!!!window.top.myData){
            $.ajax({
                dataType: "json",
                url: 'http://127.0.0.1:5743/myData.json',
                async: false
            })
            .done(function(json) {
              window.top.myData= json;
            })
        }
        doStuff();
        doStuffUsingMyData();
    });
}

I used

$.ajax()

instead of

// @include myData.js 

to load my data, in order to avoid an injection of ~1MB (size of my data) for every frame.

Thanks again!

luciopaiva commented 4 years ago

Got it, nice and elegant solution!