1

I'm writing a Chrome extension which injects some JS onto a page. My JS is stored in script.js. I use a contentscript.js to actually do the injecting (as is recommended in this SO answer). This all works fine.

My script.js uses jQuery, and therefore needs jquery.js and a bunch of plugins to be injected too. So my contentscript.js looks like this:

var a = document.createElement('script');
a.src = chrome.extension.getURL("jquery.min.js");
a.onload = function() {
    this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(a);

...4 more similiar plugin script injections...

var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
s.onload = function() {
    this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);

The sequential order in script.js is the order they must be evaluated in.

My problem is that depending on how it happens to load, I seemingly have no guarantee that jquery.min.js gets evaluated before my script.js, leading to errors like "$ undefined" and "Object does not have ... method" (for the plugins).

FYI, I know it's not some huge blocking issue because when they are all cached and load in the correct order, my extension works perfectly. That's what leads me to believe it's the loading that is the problem.

How do I go about enforcing the order I need on how my scripts are evaluated?

Community
  • 1
  • 1
Karl Barker
  • 11,095
  • 3
  • 21
  • 26

3 Answers3

2

I hope you're not literally chaining the loads together by actually writing out the same code five times, nested five levels deep. This can be solved in a pretty straight-forward manner with a queue and a single function:

function loadNextPlugin(plugins) {
  // "pop" the next script off the front of the queue
  var nextPlugin = plugins.shift();

  // load it, and recursively invoke loadNextPlugin on the remaining queue
  var tag = document.createElement('script');
  tag.src = chrome.extension.getURL(nextPlugin);
  tag.onload = function() {
    if (plugins.length)
      loadNextPlugin(plugins);
  }
  (document.head||document.documentElement).appendChild(a);
}


loadNextPlugin(["jquery.min.js", "script2.js", "script3.js", "script.js"]);
user229044
  • 232,980
  • 40
  • 330
  • 338
  • I...was. I'm really not versed in JS and it worked so didn't try to improve it :) Using this now - though I got 'document not defined' errors until I added a `;` after the `onload` function definition? – Karl Barker Jan 29 '13 at 12:38
  • Yes, there should be a semi-colon terminating the `tag.online =...` statement. This isn't tested code, I wrote it more as a guideline. – user229044 Jan 29 '13 at 13:56
  • Sure, I was just checking :) Like I say, I've not done much JS. – Karl Barker Jan 29 '13 at 16:22
1

Chain the loading of each plugin, by loading the next plugin in the onload handler of the previous one.

Barmar
  • 741,623
  • 53
  • 500
  • 612
0

Set async=false on the scripts you inject.

This is the spec-compliant way to have scripts loaded in any order, but executed in the order they're added.

From Deep dive into the murky waters of script loading:

[
  '1.js',
  '2.js'
].forEach(function(src) {
  var script = document.createElement('script');
  script.src = src;
  script.async = false;
  document.head.appendChild(script);
});

Spec says: Download together, execute in order as soon as all download.

Firefox < 3.6, Opera says: I have no idea what this “async” thing is, but it just so happens I execute scripts added via JS in the order they’re added.

Safari 5.0 says: I understand “async”, but don’t understand setting it to “false” with JS. I’ll execute your scripts as soon as they land, in whatever order.

IE < 10 says: No idea about “async”, but there is a workaround using “onreadystatechange”.

Other browsers in red say: I don’t understand this “async” thing, I’ll execute your scripts as soon as they land, in whatever order.

Everything else says: I’m your friend, we’re going to do this by the book.

Jess Telford
  • 12,880
  • 8
  • 42
  • 51