Chrome extension set to `run_at` `document_start` is running too fast?
Solution 1
Chrome extension Content scripts (run from a manifest.json
) that are run at document_start
, do fire before document.readyState
Doc 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!";
}
Solution 2
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... });
Shadow
Updated on July 09, 2022Comments
-
Shadow almost 2 years
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
todocument-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 thehead
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 ifdocument.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'sgrant 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 theload
event. I thank in advance any help provided.
EDIT: I have already tried the suggestions in the replies: using a different
run at
point, usingDOMContentLoaded
and append todocument.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 todocument.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 eitherdocumentElement
orhead
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
-
Xan over 9 years
document_idle
happens afterdocument_end
-
Marco Bonelli over 9 years@Xan quoting from the doc: "In the case of document_idle, the browser chooses a time to inject scripts between document_end and immediatly after the window.onload event fires [...]" so am I reading wrong or..?
-
Xan over 9 yearsYou're reading it right, but
window.onload
happens afterdocument_end
.window.onload
implies all resources have been loaded, anddocument_end
fires already after DOM is complete (which is earlier). -
Marco Bonelli over 9 years@Xan oh I see, alright, thanks for the clarification
-
Shadow over 9 yearsUsing
document_idle
ordocument_end
make the script load too late, making it unable to capture early events. UsingDOMContentLoaded
yields the same problems, the script is inserted/run too late. -
Pacerier almost 7 years@MarcoBonelli, But couldn't some other script on the page itself remove your listener? Thus using document_idle is better?
-
Pacerier almost 7 years@Xan, If
document_start
is inserted after the last<style></style>
, anddocument_end
is inserted inDOMContentLoaded
, Doesn't that mean that if we do not usedocument_start
,DOMContentLoaded
will not wait for<style></style>
, Hencedocument_end
will run before the time thatdocument_start
would have run? -
Marco Bonelli almost 7 years@Pacerier 1) you cannot remove a listener if you don't know the name of the function; 2) plus, the page doesn't know anything about your script, since that extension scripts run in a separated context and don't share any variable with the scripts in the page. So the answer is no.
-
Pacerier over 6 years@MarcoBonelli, But the page can do
document.replaceChild(n=document.createElement('html'),document.documentElement);n.innerHTML='replacer'
if your script is at document_start. On the other hand, if you put at document_idle, he wouldn't be able to replace your injected listeners. -
Pacerier over 6 years@Xan, Ah I'v realized now. That "CSS" does not refer to <style> and <link rel> but refers only to CSS injected by manifest.
-
Jay Croghan over 6 yearsI saw a lot of negative replies so just want to say I was playing around with this for a long time and most of the erroneous results were because I was testing the script with the developer console open which seems to give a different result to when it's closed. I got it to work, injecting jquery source script into pages from an alternative source, I'm in China so Google CDN is blocked, and I used run_at document_start and documentElement to append the script node and it works fine. A little delayed as it tries to load the other script too I may work on this later but for now it works, thanks.