48

My extension is supposed to load a content script, searchTopic.js, only after the page it's injected into has already fully loaded (yes, I have set "run_at" to "document_end" in the extension manifest), but in fact it's loading before all the DOM objects have been created (the crucial ones are created via some Javascript in the page). So, the question is, how can I wait until the page's Javascript has executed? Here's my manifest:

"content_scripts": [
  {
  "run_at": "document_end",
  "matches": ["https://groups.google.com/forum/*"],
  "js": ["searchTopic.js"]
  }
],
FractalBob
  • 3,225
  • 4
  • 29
  • 40
  • 2
    You cannot predict the future. Any solution you find is specific to the situation for which you've written the script. Try to play with the `window.onload` event, or otherwise by a kind of polling using timers to detect when the page is in the desired state. – Rob W Dec 17 '12 at 15:33
  • You cannot know when all javascript will have been executed. It is ran once when parsed, but its actions (DOM manipulation, further script loading) might happen later, triggered by any event - and I fear those were interesting for you. – Bergi Dec 17 '12 at 16:33

3 Answers3

77

"run_at": "document_end" is the equivalent to DOMContentLoaded. That is, it fires after the static HTML is loaded, but before slow images and slow finishing javascript.

So you cannot set a content script to fire after the page's JS, just by setting the manifest alone. You must code for this in the content script itself.

For content scripts, "run_at": "document_end" will fire before the onload event (unlike the default document_idle -- which can fire at unpredictable times).

So, the first step is to wait for the load event with code like this in your content script (searchTopic.js):

window.addEventListener ("load", myMain, false);

function myMain (evt) {
    // DO YOUR STUFF HERE.
}


In the case where the script you care about takes a while to finish, you will have to poll for some condition on a case-by-case basis. For example:

window.addEventListener ("load", myMain, false);

function myMain (evt) {
    var jsInitChecktimer = setInterval (checkForJS_Finish, 111);

    function checkForJS_Finish () {
        if (    typeof SOME_GLOBAL_VAR != "undefined"
            ||  document.querySelector ("SOME_INDICATOR_NODE_css_SELECTOR")
        ) {
            clearInterval (jsInitChecktimer);
            // DO YOUR STUFF HERE.
        }
    }
}
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • 1
    This is the problem: the page I'm looking at (https://groups.google.com/forum/?fromgroups=#!searchin//subject:) doesn't have any predictable condition to poll for; at least, none that I've been able to find. – FractalBob Dec 17 '12 at 16:47
  • BTW, whatever happened to answers #1 and 2? – FractalBob Dec 17 '12 at 16:49
  • 3
    It does have such a condition, or else you couldn't say that `searchTopic.js` had loaded too early. What, *exactly* are you trying to do, and what error messages do you get on the console, if any. ... What do you mean "answers #1 and 2"? One of the answers to this question was deleted by its poster. It did not solve your problem. – Brock Adams Dec 17 '12 at 17:00
  • I'm trying to determine if a topic search in a Google group yielded any results; if so, do one thing and if not, another. To do this, I look for the classname "gwt-Label". It seems that, once the page is fully loaded, when there are no search hits, document.getElementsByClassName("gwt-Label") has length 3; otherwise, it has length 2. If the page hasn't fully loaded, the above don't hold. – FractalBob Dec 17 '12 at 17:21
  • In that case, you can use a test like: `var keyNode = document.querySelector ("div[role='list'] div.gwt-Label"); if (keyNode && /No results found/i.test (keyNode.textContent) ) {...`. which only passes on searches with no results. Or just continually wait for search results and act on them as they appear. Like with [this utility](https://gist.github.com/2625891). – Brock Adams Dec 17 '12 at 17:31
  • Yes, that should work as long as the user is in the EN locale (which is why I rejected that approach originally). But what is i.test? – FractalBob Dec 17 '12 at 22:32
  • It's [the javascript, regex `test()` function](http://www.w3schools.com/jsref/jsref_regexp_test.asp), with the case-insensitive flag. – Brock Adams Dec 17 '12 at 22:43
  • See also mutation events (untested by me yet) from this answer: http://stackoverflow.com/questions/17986020/chrome-extension-javascript-to-detect-dynamically-loaded-content – HaveAGuess Mar 24 '15 at 15:11
  • @HaveAGuess, [mutation events are deprecated](http://stackoverflow.com/q/5416822/331508) (and for good reasons). – Brock Adams Mar 24 '15 at 17:56
  • @BrockAdams ok, so from that answer looks like Mutation Observers instead then right? – HaveAGuess Mar 25 '15 at 09:59
  • 1
    @HaveAGuess, yes, they would work (usually) but they often get tricky in practice. A much simpler timer works every time, is more robust and more portable. – Brock Adams Mar 25 '15 at 10:45
  • I guess I was just wanting to get moving as soon as the element Im waiting for arrived and didnt want to spin wait by some arbitrary amount as it might slow down the experience - especially for large number of elements – HaveAGuess Mar 25 '15 at 12:50
  • 1
    +1 for the timer advice. After having tried the mutation observer approach, I can testify that it gets tricky. – Mitch Jul 25 '15 at 13:49
4

script elements are executed in order. The browser even stops loading DOM content if there are script nodes in the body. This is guaranteed to work, otherwise document.write() would stop working.

So you have two solutions:

  1. Use the onload event to load your code.
  2. Add a script tag as the last element of the page (last thing in the body element). This script will be executed after all other scripts have finished and after the body has been converted to a DOM tree.
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • 2
    The script elements can be [deferred or asynch](http://peter.sh/experiments/asynchronous-and-deferred-javascript-execution-explained/). Or the script may be loaded dynamically. Or the script can be loaded, but the contained/linked code takes a bit to finish initializing. – Brock Adams Dec 17 '12 at 16:14
  • 1
    The `window.onload` waits for `async`/`defer` scripts to load and execute, so that will work better than adding a script at the bottom of the page. (See "`The download of an external script must delay the load event.`" on the [W3C's HTML API](http://www.w3.org/TR/2008/WD-html5-20080610/tabular.html).) The general problem of waiting for asynchronous script *behaviors* (Ajax, `setTimeout`, events) remains, but that is not solvable in a general way. – apsillers Dec 17 '12 at 17:05
-1

You use window.load method.

function addLoadEvent(a) {
    var b = window.onload;
    "function" != typeof window.onload ? window.onload = a : window.onload = function () {
        b && b(),
            a()
    }
}
function init() {
    alert('init');
}
addLoadEvent(init);
STEEL
  • 8,955
  • 9
  • 67
  • 89