DOMContentLoaded not working in Safari App Extension

I am trying to run JavaScript only after the page has loaded, and according to here, I should use DOMContentLoaded. However, it does not seem to work.

This is my content.js file:
Code Block
function runOnStart() {
    document.addEventListener('DOMContentLoaded', function(e) {
        document.body.style.background = "rgb(20, 20, 20)";
        document.html.style.background = "rgb(20, 20, 20)";
      
        var divElements = document.body.getElementsByTagName('div');
        for(var i = 0; i < divElements.length; i++) {
            let elem = divElements[i];
            elem.style.background = "rgba(255, 255, 255, 0.05)";
        }
    });
}
runOnStart();


If I take the code outside of the event listener, it runs fine, but a lot of the elements haven't loaded in yet so it doesn't work as it should.

The function is definitely running, but the event listener simply doesn't work. I appreciate any help you can give!
  • Please make safari abosolute

Add a Comment

Accepted Reply

This sounds like a bug. Can you send us a bug report via Feedback Assistant?

Replies

This sounds like a bug. Can you send us a bug report via Feedback Assistant?
What version of Xcode and Safari are you using?

I can confirm that in Xcode 11.0 and Safari 13.0.1 that your content scripts "works" and I am not experiencing the the issue you describe. However, I have not tested in newer versions of Safari or Xcode.

What is document.html? Are you trying to target the html element?

You should probably use document.documentElement - that line of your code is throwing an error.
Hey everyone! I have figured out a work-around to the issue. It appears that a lot of the time, the DOMContentLoaded event would fire before the event listener got called.

This workaround worked for me:
Code Block
function runOnStart() {
// Run your code here
}
if(document.readyState !== 'loading') {
    runOnStart();
}
else {
    document.addEventListener('DOMContentLoaded', function () {
        runOnStart()
    });
}


It checks to see if DOMContentLoaded has already been fired (hence the document is no longer loading), and if so, run the code. If it has not been fired yet, then the listener is initialized.

@JakeShort your solution helped me! I couldn't figure out why my JS events were not triggering ONLY on iOS. Even on the iOS Simulator it was working, but only on my iPhone things were not working.

Thank you!!

I can confirm this still is an issue, and the docs are inaccurate: https://developer.apple.com/documentation/safariservices/safari_app_extensions/injecting_a_script_into_a_webpage

Still not fixed on Xcode 14 and iOS Safari 16.

I am running macOS Sonoma and can confirm that this is still not fixed in Safari 17

That workaround that JakeShort mentioned is a good best practice to have in all of your extensions.

There is a chance that your extension will be injected into a page after DOMContentLoaded has already happened and your script should be able to handle that case.

To check this, you can check document.readyState when your content script runs. If the state is interactive or complete, then the DOMContentLoaded event has already fired and you should just run the code directly.