Greasemonkey: Support document-start script run time

Created on 25 Jul 2017  ·  17Comments  ·  Source: greasemonkey/greasemonkey

Support document-start for @run-at.

See #2483 for more detail. This may be "impossible".

All 17 comments

You can use the run_at property of the content_scripts manifest key.

I shared the pity about Chrome with you 4 years ago. Now again for Firefox. 😭

I'd like to rephrase my suggestion in https://bugs.chromium.org/p/chromium/issues/detail?id=257956

tabs.insertCSS/executeScript should insert CSS/JS until URL of the tab is set non-empty(not null, not undefined, not empty string ""), and be canceled if the tab is removed/changed

The WebExtension APIs should be improved.

@iology why did you downvote my suggestion? It is perfectly valid. Plus, one can just use the content script to insert script tags into the document that contain the user scripts.

The following should explain how to do so using my method: (should work reliably)

/**
 * @typedef UserScript
 * @property {Object} header The parsed header of a script
 * @property {String} script The raw script data
 * @property {String} type The script media type (in case a script is WebAssembly
 *                         or something other than JavaScript)
 */

/** The load state of this script. Used by the `matches()` function */
const READY_STATE = document.readyState;

(async () => {
    (await (browser.storage.local.get({scripts: []}) // Excuse this horrible code style
        .then(ret => ret.scripts)))                  // of combining `await` and `.then()`
        .forEach(/* @param {UserScript} script */ script => {
            if (matches(script.header)) {
                let scriptTag = document.createElement("script");
                scriptTag.setAttribute("type", script.type)
                scriptTag.textContent = script.script;
                document.appendChild(scriptTag);
            }
        });
})();

/**
 * Checks if this script matches this page and @run-at property.
 * 
 * @param {Object} scriptHeader The parsed header of a script
 * @return {boolean} `true` if this page matches, `false` otherwise
 */
function matches(scriptHeader) {
    // Uses window.location and READY_STATE.
    // TODO: Implement
}

@ExE-Boss By the time you retrieve scripts from extension storage part of the page would already be loaded.

I am aware of that, but I don’t really see another way to do so because of the fact that WebExtensions are inherently asynchronous.

@ExE-Boss, there's a bugzilla, https://bugzilla.mozilla.org/show_bug.cgi?id=1332273, that looks like it's progressing nicely and should help resolve the @document-start issue. Whether it'll land for FF57, I'm not sure.

I am aware of that, but I don’t really see another way to do so because of the fact that WebExtensions are inherently asynchronous.

@ExE-Boss That's literally the problem we are trying to solve.

I am trying to solve it as well, but my solution really only works well enough for #2525, because by that point the entire page has fully loaded.

Violentmonkey has this problem as well, not sure what black magic Tampermonkey uses, but it is able to inject script reliably on the real document-start.

Tampermonkey 2.9 and earlier was GPL-3.0 and published on GitHub. I’ve tried looking into it, and at first I thought it was using sync XHR, but after looking into it a bit, I have no idea how it achieves that, but it does that somehow because support for that was added in version 2.6.2767.

See here as to why Tampermonekey went closed source:

I'm not interested in pirating stuff, it won't be too hard to reverse Tampermonkey's logic but it's not legal to do that.

@Sxderp That’s only for Firefox 57, that bug might still be resolved in 58+.

FYI, Tampermonkey isn't reliable at document-start, either. There is an "instant" injection option (which is extremely hacky) that speeds things up, but on simple pages like xkcd.com, I still get a flash before my CSS (which is always injected at document start) loads. I also occasionally get a flash on other sites.

Reversing Tampermonkey's code is not a way to figure this out. Plus, well, the actual methods they use can be found in their forums, suggested by other users. Copying a method isn't a copy-vio, just copying the code itself.

But we know it's an imperfect solution, and we want a perfect one.

If a method is found to make this work, could there be a way to for a script to read at least a subset of its configuration options synchronously? As an example of the kind of issues this would solve, see https://github.com/ccd0/4chan-x/issues/1627. Here i need to disable some code on the page, but I also want to have an option not to disable the code. And many users clear localStorage at the end of each browser session.

If a method is found to make this work, could there be a way to for a script to read at least a subset of its configuration options synchronously?

Not in any short term. There's no way to communicate with the (get|set)Value storage in a synchronous manner. Perhaps some kind of injection on the part of GM wherein variables with config values are injected into the global scope of the script. That's the best I can think of, and that's not very nice.

Perhaps some kind of injection on the part of GM wherein variables with config values are injected into the global scope of the script. That's the best I can think of, and that's not very nice.

Presumably it would be part of the GM object. Something like GM.initValues.yourVariable.

Was this page helpful?
0 / 5 - 0 ratings