Fix parser-time document.write() in service worker mode - #235
Conversation
The blob URL workaround for iframe documents was also applied to document.write() calls made during parsing time causing the existing document content to be discarded. Only use the blob workaround when document.open() or document.write() actually replace the document. Fixes webrecorder#234
ikreymer
left a comment
There was a problem hiding this comment.
Nice work and great analysis!
One note, the conditions for open and write replacement are slightly different:
!oldDocumentElement || thisObj.documentElement !== oldDocumentElement;
and
oldDocumentElement !== null &&
$wbDocument.documentElement !== oldDocumentElement
Is this intentional? should they be consistent, is the null check needed at all?
After open() replaced the document, then documentElement will be null. So we don't actually need to check for change, just for null. After write() replaced the document, then documentElement could either be null (it wrote zero chars) or non-null (it wrote something) so we need to check if it changed value. For both open() and write() if documentElement was null both before and after the call then we don't necessarily know if document was replaced, but in that case there is no existing content we need to keep, so doing the blob workaround probably doesn't hurt. But not doing blob workaround might miss a replacement and leave the service worker broken.
|
It was intentional and was trying to cover cases like After open() replaced the document, then documentElement will be null. So we don't actually need to check for change, just for null. After write() replaced the document, then documentElement could either be null (it wrote zero chars) or non-null (it wrote something) so we need to check if it changed value. For both open() and write() if documentElement was null both before and after the call then we don't necessarily know if document was replaced, but in that case there is no existing content we need to keep, so doing the blob workaround probably doesn't hurt. But not doing blob workaround might miss a replacement and leave the service worker broken. |
We need to drain the buffer before applying the writes, otherwise the document.close() injected into `document.write('<script>...</script>')` will infinitely recurse.
|
I realised this: document.write('<script>...</script>');will have document.close() injected into it, effectively: document.write('<script>...; document.close();</script>')which means the |
Makes sense - I wonder if we could simplify it even further.. The document.open() call is never actually needed, and is sort of a nop, both when loading and not when loading. |
I guess an exception is calling: |
|
Added some suggested simplification here: https://github.com/nla/wombat/pull/1/changes |
…this happens only if readyState !== 'loading'
|
Oh! I was surprised that worked, but of course it does because after load I was trying to think of other ways to make it fail. Maybe if there's code that mixes document.open();
document.appendChild(document.createElement('html'));
document.write('test');
document.close();But fortunately the write does nothing in Chrome and in Firefox it actually throws "DOMException: An attempt was made to use an object that is not, or is no longer, usable". So at least that particular pattern we don't have to worry about. I think I'm pretty happy with this now. Especially after your further simplifications. |
Ha, I actually didn't realize this either, so it works but for slightly different reasons than I initially thought, maybe should adjust the comments!
I thought it would be: |
|
Based on the above, there is one additional simplification: don't actually need to check readyState at all, can just check oldDocument !== newDocument, and always first call native write(), then possibly do blob replacement. The calling native write() is needed no matter what because subsequent code might access the results of the previous write, and that can happen whether document.open() was called or not, e.g.: and should behave the same way. The new fix addresses this. |
|
Note on browser: In my testing, Firefox has the same issue as Chrome, but only incognito mode, but not in regular mode, while Safari is actually fine. Probably not worth extra detection and should always apply the blob override |
|
Passes my test cases in Chrome, Firefox and Safari. The only downside I see is this means in the blob case a written |
The blob URL workaround for iframe documents was also applied to document.write() calls made during parsing time causing the existing document content to be discarded.
Only use the blob workaround when document.open() or document.write() actually replace the document.
Fixes #234
With this patch the test case from #234, the Sydney 2000 Olympics site and https://h5p.org/presentation all work.