-
Notifications
You must be signed in to change notification settings - Fork 98
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
Conversation
The other methods were self-cleaning since 1904labs#71
Fetch cached doc headers to prevent 1904labs#100 regression.
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.
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); | ||
} |
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.
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?
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.
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.
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.
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?
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.
Yep. HTML is the correct doctype for our default style query seeing as we embed inside a HTML foreignObject
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.
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; |
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.
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.
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.
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.
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.
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.
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 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
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.
😄 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
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.
Bummer...
doesn't look much like
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
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.
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.
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.
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..
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.
Sorry, this has been so painful. Thanks for the patience.
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 pushed that as
quirks-regression
branch https://github.com/1904labs/dom-to-image-more/tree/quirks-regression and it runs all the unit tests correctlyCan 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.
Anyways, I didn't see any obvious issues. Thanks so much for your work on this!
reverthandle Bypass Trusted Types policy requirement #100.The only other method of fixing this one is
srcdoc
and that's included in the Trusted Types policy.Per Cannot take screenshot of more complex webpages #70 comments, let's run it in
toSvg
to make sure sandbox is killed in all cases.