-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Block Editor Iframe component to render in standards mode #38855
Fix Block Editor Iframe component to render in standards mode #38855
Conversation
👋 Thanks for your first Pull Request and for helping build the future of Gutenberg and WordPress, @tomasztunik! In case you missed it, we'd love to have you join us in our Slack community, where we hold regularly weekly meetings open to anyone to coordinate with each other. If you want to learn more about WordPress development in general, check out the Core Handbook full of helpful information. |
@tomasztunik Did you find any resources about this issue? Is there no other way to achieve it? I tried to use |
👋 @ellatrix - thanks for looking into this. As Were your findings around using document.write around iframe as well or were they part of DOM rendering as part of page loading flow? Before I made it to this solution I've tried to populate the initial state using Confirmed the bug to be present on latest Safari (16.1) and Firefox (97) and that the fix works there as well. |
PS. re links - I've shared some resources on quirks mode on the issue (but I guess they were bit hidden in the description!):
and additionally: |
Would it be possible to add an e2e test with some example CSS, then check the result with |
I've added E2E tests that validate that site editor iframe is running in a correct Tested to confirm that prior to code change it would fail the test with Also tried to do it as unit tests with testing-libarary or enzyme rendering but it's super though to test iframes! 😵 (Noticed the tests have failed but it was some kind of timeout on one of the instances in unrelated area of the code) |
75deed4
to
efa7458
Compare
Which service generates the actual document inside the iframe? And can we fix that service to send a correct doctype in the first place? If we replace the document with a new one (doctype + original) when |
@jsnajdr by default empty iframe has no doctype (renders in quirks mode). And it's the responsibility of content provider to set it up. It can be done either via src (and it would come from remote content provider) or srcDoc if content provider is local. In theory it should be possible to setup initial document state - including doctype - via srcDoc as mentioned but this caused errors in unrelated areas because of how iframe control is handed over to react via React.Portal. Made some attempts toward that but the surface of the change started getting out of hand and would result in a much more complex code. And regarding your concerns, this wouldn't cause the contents to setup twice as this happens before control is given back to react and before anything is rendered into the iframe, including scripts and styles as the iframe document override is synchronous and happens before these are processed. Was trying to cpu profile that change prompted by @ellatrix but it wasn't even showing up in the logs and perceptive rendering speed doesn't seem to be affected. |
Looking at the So the task to do is to setup the initial empty document with a correct
This sounds like the
Where were you calling |
Modifying existing document was my first instinct as well as mentioned here but unfortunately once document rendering mode is set appending doctype doesn't change it despite doctype being visible in DOM. I was passing static The way proposed here seemed cleaner to me from code complexity as it was doing the same just made it synchronous within the iframe binding/setup method as we were already giving the control of the iframe to react and felt like this will keep the concern encapsulated in this method. If you think this is not the case I could try to look again at the |
If you initialize the iframe as Using document.open();
document.write( '<!DOCTYPE html>' );
document.close(); I think this is going to be a little less efficient because the API is synchronous -- like other legacy things like synchronous XHR requests or The |
I think it's worth a try. Will look into it bit later today - thanks for joining in @jsnajdr and giving a new perspective. |
const { readyState, documentElement, compatMode } = contentDocument; | ||
|
||
// As srcDoc loads contents asynchronously this will cause the iframe to | ||
// load documents twice. We need to hook react to the correct contentDocument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you're testing, does the document really load twice for you? With srcDoc
, it should only load once.
When I run this little test app:
function App() {
const elRef = React.useRef();
React.useEffect(() => {
function onLoad() {
const doc = elRef.current.contentDocument;
console.log("load", doc.readyState, doc.compatMode);
}
elRef.current.addEventListener("load", onLoad);
return () => elRef.current.removeEventListener("load", onLoad);
}, []);
return <iframe ref={elRef} srcDoc="<!doctype html>" />;
}
ReactDOM.render(<App />, document.getElementById("root"));
I get only one load
event logged:
load complete CSS1Compat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking if it wasn't because first load fires before ref is set and useEffect executes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or more likely it's a browser difference - Found this exploration https://stackoverflow.com/questions/10781880/dynamically-created-iframe-triggers-onload-event-twice/38459639#38459639 it seems it might be Chrome/webkit only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On my minimal example, I get only one load event even for Firefox: load complete CSS1Compat
. I'll try it with the full Gutenberg editor.
I'm thinking if it wasn't because first load fires before ref is set and useEffect executes?
Yes, the load certainly fires right after the element is added to DOM, which is before refs are set and effects executed. But the iframe
element has the srcDoc
attribute from the very beginning, and therefore should load only once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok got it. First one is initial state not onload
event. It is not triggered by the listener but by the manual call to the setDocumentIfReady handler.
We have the comment here that load happens only in firefox
but now with srcDoc in place it will fire everywhere it seems.
Should we remove L213-215 and leave only the onload event? This way we could remove the comment here and remove or simplify the comment inside the setDocumentIfReady
and maybe remove the ready check before we initialise the iframe content.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we remove L213-215 and leave only the onload event?
Yes, it's most likely guaranteed that the load is async, and the useRefEffect
callback is always called before the load finishes. We can rely on the load
event to fire and avoid checking synchronously.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, probably good to only use the load event now. The reason we didn't use the load event for all browsers was because we could start loading the editor much quicker.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed the comment and direct call and removed the compatMode
check from ready check. I believe with how much we've simplified it the simple comment around iframe event binding and reason behind srcDoc with E2E tests explaining it a bit more and guarding for ensuring we have the right compatMode
should be enough.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you change it so we're just using the load event?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good! I posted two comments that are more about code quality rather than functionality. 🚢
@tomasztunik Can you rebase the PR branch onto the latest |
Iframe document doctype is not set by default and not having doctype will cause browser to render iframe contents in quirks mode. Appending doctype to existing document won't change iframe's rendering mode. Document.write will overwrite the document with a new one containing a correct doctype and will correctly render in stadard mode. Fixes WordPress#38854
it will visit the editor and ensure that iframe is rendering in standards CSS1Compat rendering mode. If it had BackCompat set it would render in quirks mode.
12287ca
to
1473350
Compare
@jsnajdr seems it worked 👍 |
Nice! Now only @ellatrix's 👎 blocks the merge. There used to be a "dismiss merge" action (the requested changes have been done) in the GitHub UI, but I don't see it now. |
It is under the ellipsis next to reviewer name but if it's available depends on how the repository is setup. Thanks again for great review session on this to both of you! I feel like I've recapped most of my knowledge on iframes and learned some on top :) |
Hey @ellatrix, Could you please do the final check? Appreciated! |
Everything looks green on CI. I see that all feedback was addressed so let's get this one in. |
The site editor was initiating network requests that weren't routed through the service worker. That's a known bug in Chrome, Firefox, and a few other browsers based on these two. The issue is only with the iframes using srcDoc and src="about:blank" as they fail to inherit the root site's service worker. Gutenberg loads the site editor using <iframe srcDoc="<!doctype html"> to force the standards mode and not the quirks mode: WordPress/gutenberg#38855 This commit patches the site editor to achieve the same result via <iframe src="/doctype.html"> and a doctype.html file containing just `<!doctype html>`. This allows the iframe to inherit the service worker and correctly load all the css, js, fonts, images, and other assets. Ideally this issue would be fixed directly in Gutenberg and the patch below would be removed. See #42 for more details
Gutenberg loads the site editor using <iframe srcDoc="<!doctype html>" to force the standards mode and not the quirks mode: WordPress/gutenberg#38855 This commit patches the site editor to achieve the same result via <iframe src="/doctype.html"> and a doctype.html file containing just `<!doctype html>`. This allows the iframe to inherit the service worker and correctly load all the css, js, fonts, images, and other assets. More details: #91 (comment)
Gutenberg loads the site editor using <iframe srcDoc="<!doctype html>" to force the standards mode and not the quirks mode: WordPress/gutenberg#38855 This commit patches the site editor to achieve the same result via <iframe src="/doctype.html"> and a doctype.html file containing just `<!doctype html>`. This allows the iframe to inherit the service worker and correctly load all the css, js, fonts, images, and other assets. More details: #91 (comment)
The site editor was initiating network requests that weren't routed through the service worker. That's a known bug in Chrome, Firefox, and a few other browsers based on these two. The issue is only with the iframes using srcDoc and src="about:blank" as they fail to inherit the root site's service worker. Gutenberg loads the site editor using <iframe srcDoc="<!doctype html"> to force the standards mode and not the quirks mode: WordPress/gutenberg#38855 This commit patches the site editor to achieve the same result via <iframe src="/doctype.html"> and a doctype.html file containing just `<!doctype html>`. This allows the iframe to inherit the service worker and correctly load all the css, js, fonts, images, and other assets. Ideally this issue would be fixed directly in Gutenberg and the patch below would be removed. See WordPress/wordpress-playground#42 for more details
Gutenberg loads the site editor using <iframe srcDoc="<!doctype html>" to force the standards mode and not the quirks mode: WordPress/gutenberg#38855 This commit patches the site editor to achieve the same result via <iframe src="/doctype.html"> and a doctype.html file containing just `<!doctype html>`. This allows the iframe to inherit the service worker and correctly load all the css, js, fonts, images, and other assets. More details: WordPress/wordpress-playground#91 (comment)
Description
This fixes an issue where iframe component used by Block Editor would
render its contents differently to how they were rendered on the front-end.
Iframe document doctype is not set by default if it is not provided via iframe
src
orsrcDoc
and not having doctype will cause browser to render iframecontents in quirks mode. Appending doctype to existing document won't change
iframe's rendering mode.
Document.write
will overwrite the document witha new one containing a correct doctype and will correctly render in stadards mode.
I've reviewed other areas of Gutenberg and it seems this is the only place
affected. All other places are correctly setting
<!DOCTYPE html>
.More details provided in the related issue.
Fixes #38854
Testing Instructions
Screenshots
Example error:
Example valid after applying fix:
Types of changes
Bug fix (non-breaking change which fixes an issue)
Checklist:
*.native.js
files for terms that need renaming or removal).