0

I'm writing a Google chrome extension in which I parse the <head> tags of any page loaded and look for a specific <meta> tag. A content script is used to communicate with the site, the actual logic is then done in a background script.

The question is when the background script should ask the content script to check the <meta> tags. chrome.webNavigation.onDOMContentLoaded seems to be on the safe side, but I found that often this event is fired multiple times across when loading a page. Also, the <head> might be available before chrome.webNavigation.onDOMContentLoaded hits. That all brings me to the question:

How do I best check when the <head> section of a page is loaded?

Nico Schlömer
  • 53,797
  • 27
  • 201
  • 249
  • 1
    `onDOMContentLoaded` should be fired only once per frame. If you're only interested in the top-level frame, check whether `details.frameId === 0`. If it is fired more than once per page load, then it's a bug. – Rob W Nov 05 '15 at 23:23

1 Answers1

0

Since you're injecting a content script into all pages anyway the task can be done inside it instead of always waiting for the page to be completely loaded (could take a long time depending on the connection speed and other factors).

Inject the content script at document load start and use MutationObserver attached to document.head to get the result immediately as it's inserted into DOM during page load process. The observer will look only at direct children of HEAD element so it won't hurt performance.

Some sites may add meta elements after the page is loaded - this will be caught by the observer.

  • manifest.json:

     "content_scripts": [{
        "matches": ["<all_urls>"],
        "js": ["content.js"],
        "run_at": "document_start"
    }],
    
  • content.js:

    If HEAD is present, look for the required meta element, otherwise wait for HEAD to be inserted.

    if (document.head) {
        examineHead();
    } else {
        new MutationObserver(function(mutations) {
            if (document.head) {
                this.disconnect();
                examineHead();
            }
        }).observe(document.documentElement, {childList: true});
    }
    
    function examineHead() {
        var nodes = document.head.getElementsByTagName('meta');
        for (var i = 0; i < nodes.length; i++) {
            var n = nodes[i];
            if (reportMetaNode(n)) {
                return;
            }
        }
        observeHead();
    }
    

    Observe document.head and check each new element if it's the one.

    function observeHead() {
        new MutationObserver(function(mutations) {
            for (var i = 0; i < mutations.length; i++) {
                var nodes = mutations[i].addedNodes;
                for (var j = 0; j < nodes.length; j++) {
                    var n = nodes[j];
                    if (n.localName == 'meta') {
                        if (reportMetaNode(n)) {
                            this.disconnect();
                            return;
                        }
                    }
                }
            }
        }).observe(document.head, {childList: true});
    }
    

    For example let's look for <meta name="twitter:app:id:googleplay" content="......"> and send a message to the background script with the value of content attribute.

    function reportMetaNode(n) {
        console.log(n, n.outerHTML);
        if (n.name == "twitter:app:id:googleplay") {
            chrome.runtime.sendMessage({gotMeta: n.content});
            return true;
        }
        return false;
    }
    

    You may want to also process other types of mutations like characterData and attributes because some sites may change or add the element in separate steps (createElement, setAttribute, append the child text node and so on), in which case the observer callback will be a bit more complex and subtree: true will be needed in observe() parameters.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136