Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Chrome extension set to `run_at` `document_start` is running too fast?

EDIT: Something was wrong with my Chrome browser and creating a conflict with my script, a full reinstall eliminated whatever the problem source was. If I happen to find out what was causing it I will include it in here.

EDIT2: Just to let anyone reading this in 2017 know that I haven't forgotten this and I have never had this problem since my previous edit.

EDIT3: It is 2019 and so far I've never had this problem again.


I have been learning how to create a simple Chrome extension which is a userscript port. The script works perfectly with Tampermonkey with the setting run at to document-start, all the necessary events that need to be caught from the beginning are all captured.

However, when I set the same settings in the Chrome extension I discovered that the same running setting is faster than Tampermonkey's which causes the first function to fail: (Uncaught TypeError: Cannot call method 'appendChild' of null.) since it tries to append a script element to the head section, which doesn't exist until 0.010s later.

My dirty solution so far has been to make use of a setInterval function with the timer set to 10 to check if document.head exists and then proceed with the code if the condition is true.

Is there any way that I can make this work correctly without having to resort to setInterval or maybe replicate Tampermonkey's grant none option which appears to run the userscript on the webpage context?

The following is my manifest.json file:

{
    "manifest_version": 2,
    "content_scripts": [ {
        "js":        [ "simpleuserscript.user.js" ],
        "matches":   [ "https://www.google.com/*"],
        "run_at":    "document_start"
    } ],
    "converted_from_user_script": true,
    "description":  "Chrome extension",
    "name":         "Testing",
    "version":      "1"
}

All of this could be avoided if Chrome would adopt the afterscriptexecute event, but until that happens I am stuck with the load event. I thank in advance any help provided.


EDIT: I have already tried the suggestions in the replies: using a different run at point, using DOMContentLoaded and append to document.documentElement. All were unsuccessful because: 1 and 2 makes the script miss early events, and 3 returns the same TypeError as when trying to append to document.head.

The script has to be inserted/running when document.readyState = loading or else it will miss early necessary events, but not so early to the point of being unable to append childs to either documentElementor head

An example of the code inside simpleuserscript.user.js:

var script = document.createElement("script");
script.textContent = "console.log('success')";
if(document.head) {
    document.head.appendChild(script);
} else if(document.documentElement) {
    document.documentElement.appendChild(script);
}

Console will show TypeError: Cannot call method 'appendChild' of null

like image 870
Shadow Avatar asked Jan 28 '15 06:01

Shadow


2 Answers

Chrome extension Content scripts (run from a manifest.json) that are run at document_start, do fire before document.readyStateDoc has reached interactive -- which is the earliest you want to start messing with most page elements.

However, you can inject most <script> nodes right away if you wish. Just not to document.head or document.body because they don't exist yet.
Append to documentElement instead. For example:

var s = document.createElement ("script");
s.src = "http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js";
s.async = false;
document.documentElement.appendChild (s);

Or

var s = document.createElement ("script");
s.src = chrome.extension.getURL ("MyPwnCode.js");
s.async = false;
document.documentElement.appendChild (s);

If you are adding or modifying other DOM elements, in a script running at document_start, wait until the DOMContentLoaded event like so:

document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);

function fireContentLoadedEvent () {
    console.log ("DOMContentLoaded");
    // PUT YOUR CODE HERE.
    //document.body.textContent = "Changed this!";
}
like image 60
Brock Adams Avatar answered Oct 19 '22 13:10

Brock Adams


Your problem is that, when using "run_at": "document_start", the only element that is granted to exist in the DOM is the <html> element. If you want to avoid errors relative to page load, like trying to access some element that hasn't been created yet, you'll have to either:

  • Make your script run at "document_idle" or "document_end". Although "document_end" still doesn't grant you that all the elements and resources of the page have been fully loaded (but the DOM has already been parsed), the "document_idle" keyword will give you the certainty that the DOM has been parsed and all the elements and resources have been loaded properly before your script runs.

  • Or, instead, you can continue using "document_start" wrapping your code inside a "DOMContentLoaded" listener, which will make it run when the DOM has completely finished loading and parsing, similarly to "document_idle". Here's an example:

    document.addEventListener("DOMContentLoaded", function() {
        // Run your code here...
    });
    
like image 43
Marco Bonelli Avatar answered Oct 19 '22 12:10

Marco Bonelli