Skip to content
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 quirks mode regression #110

Closed
wants to merge 6 commits into from

Conversation

zm-cttae
Copy link

@zm-cttae zm-cttae commented Jan 30, 2023

@zm-cttae zm-cttae changed the title Revert #100 to fix quirks mode regression Fix quirks mode regression Jan 30, 2023
src/dom-to-image-more.js Outdated Show resolved Hide resolved
@zm-cttae zm-cttae marked this pull request as ready for review January 30, 2023 18:14
Copy link
Member

@IDisposable IDisposable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to create test cases for each possibility?

const sandboxDoctype = document.doctype ? '<!DOCTYPE html>' : '';
const sandboxHTML = `${sandboxDoctype}${sandboxDocument.documentElement.outerHTML}`;
sandbox.setAttribute('srcdoc', sandboxHTML);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, the question I have is what does this actually do versus the original code? Seems like the path where we don't need trusted types is handled now, but what happens if we do need them?

Copy link
Author

@zm-cttae zm-cttae Jan 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the page would have thrown an error because of Trusted Types, it'll have the iframe with the #100 regression present!

If it doesn't have a Trusted Types policy that throws errors on a raw HTML setter (most pages thankfully don't), we do go ahead and coerce in the doctype. Setting srcdoc via setAttribute isn't perfect, but it is marginally better than document.write.

Admittedly document.write's destructive behavior is more suited to the use case but it's not implemented as an API well enough to be reliable.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems icky to be doing the DOCTYPE handling only in one case, but I guess because we're creating the IFRAME from the main document, it gets the right one inside?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep. HTML is the correct doctype for our default style query seeing as we embed inside a HTML foreignObject

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My thought (so poorly expressed, sorry), is that the net effect of this code when the isTrustedTypesRequired === true is that we're not actually setting the DOCTYPE of the new IFRAME at all. I am wondering if we can/should... seems that the DOM .doctype is read-only, so I doubt we can change anything since we're already synthesized the iframe up at line 1198. I guess this PR (and the one that led to it) has gone through enough churn that I am a bit gunshy about this whole code segment. Sorry.

I might be a bit less worried if we were doing anything in the (non-existent) else clause for this test... in that my mind could settle on what's supposed to be happening in either case, but I'm too close.

request.open('HEAD', document.location.href, false);
request.send();
const csp = request.getResponseHeader('content-security-policy');
isTrustedTypesRequired = csp.indexOf('require-trusted-types-for') !== -1;
Copy link

@joswhite joswhite Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To check for Trusted Type API error, instead of creating a network request, what if we try the original sandbox.contentWindow.document.write('<!DOCTYPE html>...') in the try block? Then in the catch block, we execute the fallback code.

Another idea - would document.write succeed if we used DOMPurity.sanitize first? See an example here Never mind - DOMPurify strips out the entire input to write() so it wouldn't work.

Copy link

@joswhite joswhite Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One more thing - using synchronous XMLHttpRequest is deprecated. I get the following warning in my console - "Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help http://xhr.spec.whatwg.org/". If we do stay with a network request maybe we should consider using an asynchronous one.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deep into this rabbit-hole... stumbled upon this comment https://github.com/w3c/trusted-types/blob/main/src/trustedtypes.js#L337 which worried me a bit, but I think that's only Edge 12-18 (e.g. the not-Chromium-based version) https://caniuse.com/iframe-srcdoc

Noting this here so I don't head down the same rabbit hole.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we could just look to see if there's a CSP on the currently executing script like https://github.com/w3c/trusted-types/blob/main/src/polyfill/full.js#L27

Copy link
Author

@zm-cttae zm-cttae Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄 seeing as we are in the rabbit hole ...

try-finally doesn't work for some strange reason. However we can actually use the Trusted Types API to check if a policy is needed here - spec

Sometimes require-trusted-types-for only appears in the content header for the response. If that's the case, the only way we can+should get what we want is either Trusted Types API or async XHR

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bummer...

image

doesn't look much like

image

await domtoimage.toSvg(document.body)
VM183:1221 
        
       This document requires 'TrustedHTML' assignment.
tryTechniques @ VM183:1221
ensureSandboxWindow @ VM183:1212
getDefaultStyle @ VM183:1151
copyUserComputedStyleFast @ VM183:1071
copyStyle @ VM183:367
cloneStyle @ VM183:342
Promise.then (async)
processClone @ VM183:333
(anonymous) @ VM183:266
Promise.then (async)
cloneNode @ VM183:265
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:76
Promise.then (async)
toSvg @ VM183:75
(anonymous) @ VM188:1
VM183:1236 
        
       This document requires 'TrustedHTML' assignment.
tryTechniques @ VM183:1236
ensureSandboxWindow @ VM183:1212
getDefaultStyle @ VM183:1151
copyUserComputedStyleFast @ VM183:1071
copyStyle @ VM183:367
cloneStyle @ VM183:342
Promise.then (async)
processClone @ VM183:333
(anonymous) @ VM183:266
Promise.then (async)
cloneNode @ VM183:265
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:294
Promise.then (async)
(anonymous) @ VM183:293
cloneChildren @ VM183:292
(anonymous) @ VM183:263
Promise.then (async)
cloneNode @ VM183:262
(anonymous) @ VM183:76
Promise.then (async)
toSvg @ VM183:75
(anonymous) @ VM188:1
callout:1 
        
       Access to XMLHttpRequest at 'https://www.google.com/images/hpp/Chrome_Owned_96x96.png' from origin 'https://ogs.google.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
VM183:764 
        
       cannot fetch resource: https://www.google.com/images/hpp/Chrome_Owned_96x96.png, status: 0
fail @ VM183:764
done @ VM183:738
XMLHttpRequest.send (async)
(anonymous) @ VM183:719
getAndEncode @ VM183:707
Promise.then (async)
inline @ VM183:997
(anonymous) @ VM183:1016
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
(anonymous) @ VM183:1020
(anonymous) @ VM183:1019
Promise.then (async)
inlineAll @ VM183:1014
inlineImages @ VM183:483
Promise.then (async)
toSvg @ VM183:79
(anonymous) @ VM188:1
VM183:719 
        
      
        
        
      
        
      
       
        
       GET https://www.google.com/images/hpp/Chrome_Owned_96x96.png net::ERR_FAILED 200

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Trusted Types API fn only tells us if we could be hitting the problem, it isn't a requirement check.
Looks like async XHR with HEAD method is the only sane check we could use here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API detectPolicy snippet doesn't handle trusted type policy that is set as response header on server-side. :(
Async XHR with HEAD method it is..

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, this has been so painful. Thanks for the patience.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed that as quirks-regression branch https://github.com/1904labs/dom-to-image-more/tree/quirks-regression and it runs all the unit tests correctly

Can you validate @joswhite as I don't have a fiddle or unit test that shows this.

I haven't been able to prove that the old code actually activated Quirks mode. For other <iframe> instances without DOCTYPE, I get this. Not sure why I couldn't get the old code to show me the same warning. It might depend on the environment or browser as well.

image

Anyways, I didn't see any obvious issues. Thanks so much for your work on this!

IDisposable added a commit that referenced this pull request Feb 1, 2023
This superceeds #110,  trying to fix a regression introduced by #100
IDisposable added a commit that referenced this pull request Feb 1, 2023
This superceeds #110,  trying to fix a regression introduced by #100
@zm-cttae zm-cttae closed this Feb 1, 2023
IDisposable added a commit that referenced this pull request Feb 1, 2023
* Fix the quirks mode regression
This supersedes #110,  trying to fix a regression introduced by #100
Made the ensure sandbox a bit cleaner
Also bumped to 3.0.3-rc.0
Fixed the prettier issues.
@zm-cttae zm-cttae deleted the hotfix-tosvg-remove-sandbox branch February 2, 2023 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants