5

I have a few problems with iframe usage. I'll ask one question at a time. I am working on the LaTeX.js playground and wanted to add some JavaScript to process \marginpars, but whatever I do, neither Chrome nor Firefox even attempt to load the script. It works if the exact same code is used outside of any frames.

I tried both absolute and relative paths, and I tried file, http, and https protocols. Always the same result.

Here is the relevant code:

<iframe id="preview" sandbox="allow-same-origin allow-scripts"> <html> <head> <title>LaTeX.js Show&shy;case</title> <meta charset="UTF-8"> <link type="text/css" rel="stylesheet" href="css/katex.css"> <link type="text/css" rel="stylesheet" href="css/article.css"> <link type="text/css" rel="stylesheet" href="css/error.css"> <script src="https://latex.js.org/js/base.js"></script> </head> ... 

The problem is base.js. The developer tools show no error in the console, no entry in the sources tab, and no entry in the network tab.

UPDATE: as I said in the comments, the code above is the result from modifying the DOM using JavaScript; however, setting iframe.contentWindow.document.documentElement does not seem to trigger loading the scripts in <head>, only Option 2 in the answer works for me.

1
  • Unless the spec has drastically changed, I'm pretty sure you can't put content in iframes that way. Have you tried using a data URL instead? Commented Jun 1, 2018 at 23:03

1 Answer 1

6

Option 1: Using DOM

For me iframe.contentWindow.replaceChild(newDocumentElement, iframe.contentWindow.documentElement) didn't work (replaceChild wasn't a method, and iframe.contentWindow.documentElement.parentNode is null). I had success replacing the iframe's head and the body:

var iframe = document.getElementById('preview'); var iframeDocument = iframe.contentDocument; var head = document.createElement('head'); var body = document.createElement('body'); var library = document.createElement('script'); //library.src = 'https://latex.js.org/js/base.js'; library.src = 'https://d3js.org/d3.v4.min.js'; head.appendChild(library); body.appendChild(document.createTextNode('Hello, World!')); iframeDocument.head.parentNode.replaceChild(head, iframeDocument.head); iframeDocument.body.parentNode.replaceChild(body, iframeDocument.body); 

Here's a fiddle: https://jsfiddle.net/zkb91faf/

You can verify that the d3.v4.min.js is loaded properly by navigating to the iframe in the debugger (See here for Chrome, and here for Firefox), and typing d3 into the console.

Option 2: Using src/srcdoc

I'd be remiss if I didn't mention iframe.srcdoc. It looks like you're just using static HTML, so it may be appropriate.

You'd just set it via something like:

iframe.srcdoc = newDocumentElement.outerHTML;

Alternatively, if you want to support older browsers you can set it via iframe.src and a data URI:

iframe.src = 'data:text;base64,' + btoa(newDocumentElement.outerHTML); 

If you need unicode support, refer to https://developer.mozilla.org/en-US/docs/Web/API/WindowBase64/Base64_encoding_and_decoding

Note Using Relative URLs:

You probably will run into problems using relative URLs, like that in <link type="text/css" rel="stylesheet" href="css/article.css">, because your iframe won't have the correct URI to it. You would probably need to use absolute URLs.

Update: Adding script tags after a document is loaded:

It looks like the culprit is in base.js, where you attach and run code based on the DOMContentLoaded event. The DOM is already loaded, so this event won't fire. What you can do is check whether the window is already loaded, and then run your initialization code, if it is. Via https://stackoverflow.com/a/9457996/6184972:

if (document.readyState === "complete" || document.readyState === "loaded") { completed(); } 

In this case, you may need to replace the body before the head, just in case if completed() does any direct manipulations of the DOM.

Sign up to request clarification or add additional context in comments.

8 Comments

Oh God! iframe.srcdoc works and really loads the script! I just don't like it, because it makes the browser reload all CSS and the script everytime a key is pressed. I will try to get Option 1 to work.
I don't get it :( I just cannot get Option 1 to work. But I can use Option 1 if head didn't change, Option 2 otherwise. So I will accept your answer if no one else can explain why Option 1 doesn't work.
I don't quite understand. Are you saying it works one time, and then you try to change it, and then it doesn't work? Or does it not work at all?
Ah, I see where I confused you: Option 1 never works with regards to loading scripts, Option 2 always does but obviously reloads all css and js everytime you use it, even if only a word in the body changed. So I just check if innerHTML of <head> changed and only then use Option 2, otherwise update parts of the DOM only with Option 1.
Hmmm, Option 1 works for me for loading scripts (in my example, I verified it by seeing whether d3 loaded). How are you replacing your document? I've tried the above example, and also using iframe.contentDocument.documentElement.parentNode.replaceChild(newDocumentElement, iframe.contentDocument.documentElement); and they both work for me. How are you checking whether your script loaded?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.