I use this to preview an e-mail
When I dynamically write HTML to an iframe somehow the <body>
tag is omitted? The body tag holds the font-family
etc. but now the whole tag is gone and the HTML document is not shown correctly
const iframe_content = $('#'+iframe_id).contents().find('html');
iframe_content.html(data.html);
contents of data.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>@media (prefers-color-scheme: dark){
.body_inner{background:#000 !important}
.content{border:0}
.content_inner{background:#000 !important;color:#fff !important}
}</style>
</head>
<body style="margin:0;padding:0;font-family:arial,helvetica,garuda">
<div>first element</div>
</body>
</html>
After the HTML is written to the iframe, the source of the iframe looks like this (the body tag is gone!)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="color-scheme" content="light dark">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>@media (prefers-color-scheme: dark){
.body_inner{background:#000 !important}
.content{border:0}
.content_inner{background:#000 !important;color:#fff !important}
}</style>
</head>
<div>first element</div>
</html>
I have tried to validate the HTML via https://validator.w3.org/ and no errors
2
Answers
The reason is that jQuery’s
.html(value)
method is not intended to be used for setting content with a (top-level)<html>
tag. We can see this in the jQuery source code. When callingiframe_content.html(data.html);
, the following calls are made inside the jQuery library:html()
callsaccess()
, which calls (via callback)append()
, which callsdomManip()
, which callsbuildFragment()
, which executes the following code, whereelem
is thedata.html
you passed as argument, andfragment
is a documentFragment:So
tmp
is a<div>
element (asappendChild
returns the argument).tag
is set to "html", andwrap
to[0, "", ""]
, andjQuery.htmlPrefilter(elem)
just returnselem
. So in short, this is executed:The problem is HTML does not allow a
<div>
element to have an<html>
or<head>
or<body>
element as child, so the DOM (not jQuery) will not create those DOM elements as intended.This is not the end of the jQuery processing, but already at this point we can see that information has been lost.
Work around
As jQuery’s
.html(value)
method cannot do the job, bypass jQuery and setinnerHTML
directly. Replace this:with this:
And now it will work.
Have you tried using a Shadow DOM?
All styles are encapsulated just like an iframe, but you also get the added benefits of naturally expanding content (compared to the fixed height of an iframe) and faster render times.
Example usage: