Search code examples
javascriptgoogle-chrome-extensionchrome-extension-manifest-v3

chrome extension content script not working when url change but only works after refresh


I'm developing a chrome extension to make some changes on Github site while the URL postfix matches the source code type (for example, xxx.cpp)

I expect the content.js will run every time the tab finishes loading the DOM elements. However, it only runs when I press the refresh button. If I click the hyperlinks and go to another URL, content.js won't run.

What did I do wrong? Thanks.

Following are the code.

manifest.json

{
    "name": "My Chrome Ext",
    "description": "change appearance of source code on Github",
    "version": "0.1",
    "manifest_version": 3,
    "permissions": ["storage", "activeTab"],
    "action": {},
    "content_scripts": [
        {
            "matches": ["*://github.com/*cpp"],
            "all_frames": true,
            "js": ["content.js"],
            "run_at": "document_end"
        }
    ]
}

content.js

window.alert("start running content.js");

Solution

  • The Chrome extension content script will only load when a completely new webpage matching the URL specified in your manifest is loaded. In this era of single page web applications, many websites, including GitHub, use Javascript frameworks and Ajax calls to only update parts of the existing webpage content as the user navigates around the site. Even though the address bar is being updated, most of the time no actual page loads are being executed, so your chrome extension won't trigger.

    This improves the performance of the website, but it does mean that browser extensions can't always depend on their content scripts being loaded even if the browser is displaying a matching URL.

    My solution? Enable the extension for the entire site and the gate the content script functionality behind a MutationObserver that checks window.location.href every time the callback is called:

    function addLocationObserver(callback) {
    
      // Options for the observer (which mutations to observe)
      const config = { attributes: false, childList: true, subtree: false }
    
      // Create an observer instance linked to the callback function
      const observer = new MutationObserver(callback)
    
      // Start observing the target node for configured mutations
      observer.observe(document.body, config)
    }
    
    function observerCallback() {
    
      if (window.location.href.startsWith('https://github.com')) {
        initContentScript()
      }
    }
    
    addLocationObserver(observerCallback)
    observerCallback()
    

    I also invoke the callback immediately the content script is called to ensure the script triggers correctly on a page reload. You might have to play with the observer configuration to ensure the observer triggers the callback at all the right times.

    Note, the target node for the observer be one that remains in the document unless the entire page is refreshed. If it's overwritten, you will lose the observer and your script won't be triggered. In my case the document.body works just fine.