Userscript works only on pages served from backend, but not on frontend in a SPA way
Asked Answered
N

4

5

I have the following userscript I run on Greasemonkey/Tampermonkey.

I run it on facebook.com which serves some of the webpages from backend, in bootstrapping, and some others on the fly, in front-end, via HRO, just as a Single Page Application (SPA) would.

// ==UserScript==
// @name        facebook
// @namespace   nms
// @include     http://*.facebook.com/*
// @include     https://*.facebook.com/*
// @version     1
// @grant       none
// ==/UserScript==

setTimeout( () => {
    // generalStuff:
        document.querySelectorAll(' #left_nav_section_nodes, .fbChatSidebar ').forEach( (e)=> {
            e.style.visibility = "hidden";
        });

}, 1000);

If I run this script on console, even in HRO based webpages, it runs fine, but when runned from Greasemoneky/Tampermonkey it won't run in these particular webpages.

How could I make the script to work without problem on SPA-like webpages as well?

Nucleoplasm answered 1/8, 2017 at 0:9 Comment(0)
N
6

In such a case when setTimeout, setInterval, and event delegation doesn't work by themselves, it is possible to push a state that implements them into the memory, then replacing the existing state with it, so that the webpage's DOM content will change.

Here's a code used to replace data that was loaded with AJAX instead directly from PHP:

let utilityFunc = ()=> {
    var run = (url)=> {
       // insert your code here
    };

    var pS = window.history.pushState;
    var rS = window.history.replaceState;

    window.history.pushState = function(a, b, url) {
        run(url);
        pS.apply(this, arguments);
    };

    window.history.replaceState = function(a, b, url) {
        run(url);
        rS.apply(this, arguments);
    };

utilityFunc();

That's what I understood from reading here.

Nucleoplasm answered 20/8, 2017 at 16:40 Comment(1)
Something you might also want to do is have your run function return a cleanup function that you can call in your custom push/replaceState so you don't have multiple setIntervals running at the same timeScarlettscarp
B
1

From reading this documentation, this code would work for SPA changing the location:

// ==UserScript==
...
// @match        https://subdomain.domain.tld/*
// @match        http://subdomain.domain.tld/*
// @grant        window.onurlchange
// ==/UserScript==

(function() {
    'use strict';
    if (window.onurlchange === null) {
        window.addEventListener('urlchange', (info) => {
            if (Object.values(info)[0].includes("/path/to/certainlocation")) {
                // your code here
            }

        });
    }
})();

Note this would not work if you visit the certain location directly, but that could easily be fixed, or you could create another userscript that only matches that certain location, eg:

// @match       https://subdomain.domain.tld/path/to/certainlocation
// @match       http://subdomain.domain.tld/path/to/certainlocation
Bonze answered 4/4, 2023 at 7:4 Comment(0)
B
0

For a similar use case, I used MutationObserver and checked node.baseURI of each node in each mutation.addedNodes.

(async function() {
    'use strict';
    const selector = '.quiz--answered';
    const observer = new MutationObserver(mutations => {
        if (!mutations.some(mutation => Array.from(mutation.addedNodes).some(node => node.baseURI.includes('/quiz/')))) {
            return;
        }
        const elm = document.querySelector(selector);
        if (elm) {
            // work with elm
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
})();
Barren answered 26/7, 2022 at 10:47 Comment(0)
P
0

I have stumbled upon this problem several times myself, I ended up writing a simple lib to deal with this. It allows me to run the script on URL changes, define the URLs with wildcards and wait for specific element if I want to. It's called spa-runner.

import { run } from "@banjoanton/spa-runner";


const handler = () => {
    console.log("hello world!");
}

const config = {
    urls: ["http://*.facebook.com/*"],
    runAtStart: true,
};

const unsubscribe = run(handler, config);
Prolactin answered 19/1, 2023 at 9:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.