iframeaddeventlistenercontent-scriptdomcontentloadedsafari-app-extension

How to properly inject an iframe to the DOM with Safari App Extensions?


I'm porting my WebExtension to Safari using a Safari App Extension.

My extension is an iframe inject in the DOM so I was thinking to have something like this injection script loaded as a SFSafariContentScript.

document.addEventListener('DOMContentLoaded', function(e) {
    var newElement = document.createElement("script");
    newElement.src = safari.extension.baseURI + "bundle.js";
    document.body.insertBefore(newElement, document.body.firstChild);
});

The thing is the DOMContentLoaded event seems to be triggered again when my iframe is injected which results in an infinite loop.

This behaviour seems to be different from what you would expect from a simple injection script with two files index.html and index.js that works as expected.

───────┬───────────────────────────────────────────────────────────────────
       │ File: index.html
───────┼───────────────────────────────────────────────────────────────────
   1   │ <html>
   2   │   <head>...</head>
   3   │   <body><div>...</div></body>
   4   │   <script src="index.js"></script>
   5   │ </html>
───────┴───────────────────────────────────────────────────────────────────
───────┬───────────────────────────────────────────────────────────────────
       │ File: index.js
───────┼───────────────────────────────────────────────────────────────────
   1   │ document.addEventListener('DOMContentLoaded', function(e) {
   2   │   var newElement = document.createElement('script');
   3   │   newElement.src = './bundle.js';
   4   │   document.body.insertBefore(newElement, document.body.firstChild);
   5   │ });
───────┴───────────────────────────────────────────────────────────────────

If I replace the addEventListener by a setTimeout of a few seconds in my Safari App Extension, my extension is properly injected and works well but choosing an arbitrary time to inject the iframe feels dirty to me.

How to properly inject an iframe to the DOM with Safari App Extensions?


Solution

  • Wrapping my code between this condition did the trick it was on Apple's documentation in the Inject Scripts part.

    if (window.top === window) {
        // The parent frame is the top-level frame, not an iframe.
        // All non-iframe code goes before the closing brace.
        document.addEventListener('DOMContentLoaded', function(e) {
            var newElement = document.createElement("script");
            newElement.src = safari.extension.baseURI + "bundle.js";
            document.body.insertBefore(newElement, document.body.firstChild);
        });
    }