Skip to content

undefined document variable during initialisation of Sizzle when injected as content script of a Chrome extension  #3333

@reenberg

Description

@reenberg

Description

I'm toying with an extension to chrome, and during this i have encountered issues when loading jQuery for use in content scripts. The content scripts execute in what chrome calls a 'isolated world', i.e., it may see all DOM object, but it is isolated from any scripts that was on the page they are injected into (and vice versa).

Currently i have isolated my problems down to a race condition, that seems(?) to arise due to the fact that my content script (currently only jQuery) is injected into the page before the DOM is fully initialised(?) or something like that.

  • What do you expect to happen?
    The jQuery script being injected into the browsed page without any errors, such that it may be referenced from other content scripts.

  • What actually happens
    I have tried to copy/paste and format the output i get from the chrome console. As noted before, this error only comes about 80% of the times. So if you try to reproduce, make sure to refresh the site a few times. It seems to always work on small pages like google.com, however on 'big' pages like newspapers, twitter, facebook, etc it seems to always fail with the below error:

    • jQuery 1.12.4

      jquery-1.12.4.js:940 Uncaught TypeError: Cannot read property 'createElement' of undefined
          assert                  @ jquery-1.12.4.js:940 
          (anonymous function)    @ jquery-1.12.4.js:2662
          (anonymous function)    @ jquery-1.12.4.js:2713
          (anonymous function)    @ jquery-1.12.4.js:34
          (anonymous function)    @ jquery-1.12.4.js:38
      
    • jQuery 2.2.4

      jquery-2.2.4.js:906 Uncaught TypeError: Cannot read property 'createElement' of undefined
          assert                  @ jquery-2.2.4.js:906
          (anonymous function)    @ jquery-2.2.4.js:2628
          (anonymous function)    @ jquery-2.2.4.js:2679
          (anonymous function)    @ jquery-2.2.4.js:34
          (anonymous function)    @ jquery-2.2.4.js:38
      
    • jQuery 3.1.1

      jquery-3.1.1.js:927 Uncaught TypeError: Cannot read property 'createElement' of undefined
          assert                  @ jquery-3.1.1.js:927
          (anonymous function)    @ jquery-3.1.1.js:2746
          (anonymous function)    @ jquery-3.1.1.js:2797
          (anonymous function)    @ jquery-3.1.1.js:36
          (anonymous function)    @ jquery-3.1.1.js:40
      

    I did some debugging in the 2.2.4 version

    From the above errors, it is kinda clear (and i debugged it), that document is somehow (sometimes) ending up as undefined.

    905: function assert( fn ) {
    906:    var div = document.createElement("div");
    907:

    I boiled it down to the internals of Sizzle, where it should set the internal document variable, which is used in the above assert function. However, this setDocument() function sometimes returns early if "doc is invalid or already selected"

    2623: // Initialize against the default document
    2624: setDocument();

    By printing the document variable before and after, i noticed that it is sometimes still undefined after this function, which leads to the above mentioned uncaught type errors.

    From what i can debug, it all comes down to this

    1042:   // Return early if doc is invalid or already selected
    1043:   if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
    1044:       return document;
    1045:   }

    Printing doc, document, doc.nodeType and doc.documentElement to the console, always yielded in

    • doc being a HTMLDocument object,
    • document being undefined, as it hasn't been initialised yet (we are currently doing it),
    • doc.nodeType equal to 9,
      but
    • doc.documentElement was either a HTMLHtmlElement (then obviously it worked), or null and then obviously it didn't work, as it would 'return early' with document still being undefined.
  • Which browsers are affected?

    Currently I have tested this on a Debian machine (Linux evelin 4.6.0-1-amd64 Accumulated, not yet pulled commits #1 SMP Debian 4.6.4-1 (2016-07-18) x86_64 GNU/Linux) with Google Chrome (stable 53.0.2785.116-1). As this is done using a Chrome extension to inject jQuery into the running page i don't really have a means to reproduce this in any other browser.

I don't know if this is a Chrome bug or jQuery, as it seems timing related and that I don't know enough about javascript to say for sure what can be 100% expected to be initialized by the browser before any script is being run. However currently i have fixed it by placing some busy looping code in the top of the jQuery script, to make sure it is delayed just a few fractions of a sec, that it loads without any errors.

Link to test case

You need a empty directory. In here you place this "manifest.json" file:

{
  "manifest_version": 2,
  "name": "jQuery MWE",
  "version": "0.1",

  "background": {
    "persistent": false,
    "scripts": [
      "eventPage.js"
    ]
  },

  "permissions": [
    "declarativeContent",
    "<all_urls>"
  ]
}

and you then place this "eventPage.js" file along side (which is responsible for injecting the jQuery file as a content script into the maching web page, which here is any page you browse).

var show_page_action = {
    conditions: [
        // https://developer.chrome.com/extensions/declarativeContent#type-PageStateMatcher
        new chrome.declarativeContent.PageStateMatcher({
            pageUrl: {
                urlMatches: '.*',
                //schemes: ['https'],
            }
        })
    ],
    actions: [
        new chrome.declarativeContent.RequestContentScript({
            js: ["jquery-2.2.4.js",],
        })
    ],
};

// Make sure our declarativeContent rules are up to date.
chrome.runtime.onInstalled.addListener(function (details) {
    chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
        chrome.declarativeContent.onPageChanged.addRules([show_page_action])
    })
});

Then of cause you need the jQuery file (above i have used "jquery-2.2.4.js") and then you just need to load the directory as a chrome extension.

When the extension is loaded, go browse any webpage with the chrome developer tools/console open, and refresh the page a few times until you see the above mention uncaught type error. Most of the times google.com will work just fine as it seems to load fast enough, so try something with a bit more content on.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions