-
-
Notifications
You must be signed in to change notification settings - Fork 420
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
Compatibility overhaul #92
Conversation
Wow, this took a loooong time to make, and it is huge. I'm sorry for that. :/ |
I'm sure it would have taken you a long time to make it. Need to test this, but not right now. I tried this two on my Android using Brave Did work just fine. But when i tried it out on my desktop chrome browser i started to notice an error. It is not something wrong with the streamsaver or your code but it's the stream polyfill. You see, chrome have already implemented Transfarable, Transform & Writable streams so it's likely that the polyfill is not needed then, but it overwrites the global stream apis anyway. That causes a problem. readable streams are longer no longer transfarable even doe the variable It's also a problem with a mix of native+polyfilled version rs = new ReadableStream() // polyfilled
ws = new WritableStream() // polyfilled
rs.pipeTo(ws) // ok (polyfilled <-> polyfilled)
rs = await new Response('').body // native
rs.pipeTo(ws) // fail (native <-> polyfilled) Think we should do something about this problem, eg store the native TransformStream before the polyfill loads or something. The polyfill is still required by does that don't have WritableStream. Some browser only started out with ReadableStream |
Oh, I see you have the "Experimental Web Platform features" flag enabled to test the transferable streams on Chrome. I hadn't thought of testing that, so I didn't try to fix that problem. I have now included a very small commit to fix that. It works on my Google Chrome v73.0.3683.86 32bits with the "Experimental Web Platform features" flag enabled. I have basically renamed and used the transferableSupport compatibility variable to store the compliant TransformStream class if transferable streams are supported, and then I use it to create the TransformStream. |
I've just noticed that the anchor links from the Table of Contents weren't working. They are now. |
also notice that the stream polyfill also has a ponyfill, another idea that comes from some other packages are like import fetch from 'node-fetch'
fetch.promise = bluebird what if we made that for streamsaver? streamsaver.writableStream = require('web-stream-polyfill/ponyfill').WritableStream Thoughts? |
I think that is a nice idea. :) In addition to that, in order to easily support older browsers, we could also check for the global I think we should still keep a reference to the TransformStream class from commit 4f52692, since this way many people that use the polyfill would still have it working (as long as they load Do you agree? Do you want me to include those changes on this PR? |
Yaa i agree Streamsaver.writablestream = window. Writablelestream || WebStreamPolyfill.Writablestream |
If you could include it it would be great Need to update the readme also... Maybe do the same for transform also? Streamsaver.transform = something |
Done. Also, all the Examples for testing have been updated to use the ponyfill instead of the polyfill. Edit: Didn't see your comment about the readme and the transform... I'll do those too. |
Do you want me to switch the polyfill for the ponyfill on the readme and on the example? Edit: Sorry about the force-pushes bellow. I was just fixing some typos and identation on the transform commit, and didn't want to leave a bunch of commits behind. |
3271ecb
to
f5f0a0c
Compare
can leave the readme out until we make a new release on gh-pages and on npm and Tag it |
Sorry for the time without advancing this, I have another project that is taking up all of my time, but it should be done in about two days and I'll get back to this. |
It's okey. Don't feel pressured. |
Ok, so... Just found another problem. Apparently, Chrome is also picky on security. Turns out the You can (not anymore) try it on the HTTP example on Chrome: it should never start the download. Make sure the old version isn't cached. I think maybe we'll have to open the download on a popup there. On Firefox it seems to work the way it is now in every context, and the other contexts seem to work fine on Chrome. I'll do some testing with the popup. Edit: It seems to be working with the popup on HTTP on Chrome. I'll update all the tests, clean up all the browsers and test everything again. |
And yet another problem: It seems Firefox sometimes won't execute intervals in time (apparently due to load, I think), so the Service Worker was dying before the pinging happened. I'm thinking of changing the delay for the ping interval on Firefox to 10secs, so there is no chance it doesn't happen at least once in 30 seconds. What do you think? |
I did know that already, hence why i used a popup in the first place. the spec says if the top origin is not a secure context it should prevent any service worker from installing |
I knew we couldn't install Service Workers there, I just didn't know a Service Worker installed on a popup would be incapable of intercepting requests made there. It intercepts them fine on Firefox. Anyway, what do you think about the delay on the ping interval on Firefox? That seems to be the only thing left to fix before it all works. |
fine by me |
Cool! All done? Any breaking changes that require a major bump? Summary of what have change? |
Well, I think it should all work now, but I would like to run all the tests again on all platforms to be sure. I'll be done with that in one hour, I think. What has changed on this PR is all listed on Changes to the code and Changes to functionality. Just ignore the stuff about navigating to the download link, as that has been removed. |
Ok, tests done. Found a couple of peculiarities this time:
|
I have updated the PR description to reflect the recent changes. I have also changed all examples to include the code from |
Thanks for this project, it's working great for us. Will you be creating a new release with this compatibility overhaul? |
Have some more things i have to deal with before making a new release |
* WritableStream is not necessary * Update README.md * Compatibility overhaul (jimmywarting#92) * Compatibility overhaul * Use transferable TransformStream regardless of polyfill loaded after StreamSaver.js * Allow setting WritableStream class and check for ponyfill * Allow setting TransformStream class * lock TransformStream * updated the example.html with ponyfill * Use iframes to download * sw.js claim immediately * now using isSecureCotext checking to decide if it should use popup or iframe to install sw.js * Start download on popup for insecure pages not on Firefox * Change ping interval delay on Firefox to 10secs * Tunnel readableStream through MessageChannel Main -> MS -> sw.js closes jimmywarting#94 * Update README.md * Update README.md * Update README.md
Table of Contents
<iframe>
moz-extension://*
Introduction
This PR is meant to introduce full support to Firefox in all contexts while maintaining (and lightly improving) the existing Chrome/Opera support.
In the process of making this I have discovered and extensively tested many methods of doing each step StreamSaver.js does in all contexts/browsers, and have isolated the best method for each.
I am considering four StreamSaver.js steps:
PS: Step 4 should be unnecessary if Transferable Streams are supported. In step 2, instead of sending only the message port, we would also send the stream.
I am also considering four contexts:
Why is Firefox harder
Firefox requires some stronger methods mainly because of the following reasons:
moz-extension://*
URLs on Firefox - they can only start Service Workers from external HTTPS sites;window.open
;The first two reasons imply that Web Extensions on Firefox have to either start the Service Worker from a popup (outside of the Web Extension's process, since it will be from a tab with an external HTTPS site) or from the
<iframe>
(inside the Web Extension's process).But since the download link will always be from an outside HTTPS URL, if you open it on a popup it will be loaded from outside of the Web Extension's process.
If the Service Worker was started from inside the
<iframe>
, it won't be available for that tab's process, and the 404 page will be shown.However, if you load the download link inside another
<iframe>
, it will still be inside the Web Extension's process, and it will have the Service Worker available.That way we can have tabs with Web Extension's
moz-extension://*
pages start both the Service Worker and the download without opening any popups.Too bad the background page can't start a download from the
<iframe>
on Firefox 😣On a side note, if it were possible to start a Service Worker from
moz-extension://*
URLs, if a download URL from that protocol were loaded on a tab then the Web Extension's process would be used, and it would have access to the Service Worker started from the<iframe>
.But for that the 1344561 bug on bugzilla has to be fixed first.
The fourth reason means popups have to be opened from the background page using Web Extension exclusive functions, like
chrome.tabs.create
.Those functions don't provide an easy way to pass a message port to the popup, and the only way I could find of doing that was through a Shared Worker started from the popup iself.
Last but not least, since the closed popup can't keep the Service Worker alive forever on Firefox, we need another way of doing that. We can't ping the Service Worker from HTTP pages (because of security restrictions) or from Web Extension's pages if the Service Worker is started from a popup (because of different processes).
For those situations the only way I could find to ping the Service Worker without keeping a popup open was through a Shared Worker.
Compatibility table
I know this is a lot of text. In an attempt to facilitate the understanding of everything this PR does and why, I have made a (very ugly 😥) table with collapsable cells.
You can click on a cell to read an explanation of what the method does and why the other methods are worse than the chosen one (click on them to see the reason).
Firefox
Chrome/Opera
Start
Service
Worker
mitm.html
from a domain on HTTPS using thechrome.tabs.create
function.Worse alternatives:
mitm.html
from a domain on HTTPS outside of the Web Extension), but the Service Worker is trapped inside the extension's process and then it is impossible to start the download, at least without an ugly<iframe>
inside a popup, which requires loading twice before the download starts (once for the popup page, and again for the download link inside the<iframe>
).One advantage of this, however, is that it opens only one popup instead of two, but the same can be accomplished if we reuse the same port between downloads (maybe in a future PR).
window.open
from the background page.mitm.html
from a domain on HTTPS inside an<iframe>
.Worse alternatives:
mitm.html
from a domain on HTTPS outside of the Web Extension), but it opens popups, which is not necessary.One small advantage of this, however, is that it doesn't require starting the download from the
<iframe>
.mitm.html
from a domain on HTTPS outside of the Web Extension), but it requires user interaction and opens popups, both of which are not necessary.mitm.html
from a domain on HTTPS in a popup using thewindow.open
function.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and the chosenwindow.open
function facilitates communication with themitm.html
page.<iframe>
on HTTP pages are incapable of interacting with Service Workers.mitm.html
inside an<iframe>
.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and it opens popups, which is not necessary.mitm.html
inside an<iframe>
.Worse alternatives:
mitm.html
inside an<iframe>
.Worse alternatives:
mitm.html
in a popup using thewindow.open
function.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and the chosenwindow.open
function facilitates communication with themitm.html
page.<iframe>
on HTTP pages are incapable of interacting with Service Workers.mitm.html
inside an<iframe>
.Worse alternatives:
chrome .tabs.create
function is only available for Web Extensions, and it opens popups, which is not necessary.Send messa- ge port
<iframe>
on the background page.Worse alternatives:
<iframe>
alternative to starting the Service Worker for more information.window.open
on Firefox and the tabs created withchrome.tabs.create
have no way of receiving message ports directly.iframe.contentWindow.postMessage
.Worse alternatives:
window.open
to start the Service Worker, but doing that is not necessary.popup.postMessage
.Worse alternatives:
<iframe>
alternative to start the Service Worker for more information.iframe.contentWindow.postMessage
.Worse alternatives:
window.open
to start the Service Worker, but doing that is not necessary.iframe.contentWindow.postMessage
.Worse alternatives:
window.open
to start the Service Worker, but doing that is not necessary.iframe.contentWindow.postMessage
.Worse alternatives:
window.open
to start the Service Worker, but doing that is not necessary.popup.postMessage
.Worse alternatives:
<iframe>
alternative to start the Service Worker for more information.iframe .content Window .post Message
.Worse alternatives:
window .open
to start the Service Worker, but doing that is not necessary.Start down- load
chrome.tabs.create
function, and remove it using thechrome.tabs.remove
function after the download starts.Worse alternatives:
It would be possible to have the
<iframe>
inside a tab created withchrome.tabs.create
, but that is bad. See the<iframe>
alternative to start the Service Worker for more information.window.open
from the background page.<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and opening a popup is not necessary.<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and opening a popup is not necessary.<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
window.open
function, and close it after the download starts.Worse alternatives:
chrome.tabs.create
function is only available for Web Extensions, and opening a popup is not necessary.<iframe>
on insecure pages, so the download can't be started this way.<iframe>
with the download link, and remove it after the download starts.Worse alternatives:
chrome .tabs.create
function is only available for Web Extensions, and opening a popup is not necessary.Keep Service Worker alive
* The Shared Worker must have been started by the popup, otherwise it will reside in the Web Extension's process and it wont work.
Worse alternatives:
<iframe>
, but that would create problems. See the<iframe>
alternative to starting the Service Worker for more information.<iframe>
withmitm.html
.Worse alternatives:
Worse alternatives:
<iframe>
alternative to start the Service Worker for more information.<iframe>
withmitm.html
.Worse alternatives:
<iframe>
withmitm.html
.Worse alternatives:
<iframe>
withmitm.html
.Worse alternatives:
mitm.html
.* Only works because of a bug.
Worse alternatives:
<iframe>
alternative to start the Service Worker for more information.<iframe>
withmitm.html
.Worse alternatives:
Changes to the code
Here I shall try to explain how the code was changed and why.
StreamSaver.js
Three new detections were added alongside the secure context detection:
moz-extension:
protocol detection, background page detection and Firefox detection. Those are necessary for the branching that happens later.The secure context detection now results in true for Web Extensions pages on Firefox that are on open tabs (the background page is excluded). This facilitates the branching, since Web Extension pages on tabs on Firefox should function in a very similar way to the secure pages.
A new parameter was added to indicate the URL of the
ping.html
page, used to connect to the Shared Worker needed in some contexts.The code that was responsible for creating and sending a message to the
<iframe>
on HTTPS pages was put in its own function, so it can be used in other places where the<iframe>
is needed (like for the<iframe>
withping.html
).The code that was responsible for starting the download was merged into one function with the code that would open the popup. This function loads an URL in the correct place, which can be:
window.open
;chrome.tabs.create
;<iframe>
.This is where most of the branching actually happens.
A
Math.random()
value is now inserted into the hash of themitm.html
popup created by the background page of a Web Extension on Firefox. This hash serves two purposes:sw.js
The message port received when registering a download is now stored as a property of the stream.
This is later used to notify StreamSaver.js when the Service Worker is used to start the download (this message triggers the removal of the download link's popup/
<iframe>
when necessary).The
'Mocking a download request'
message is removed so it won't interfere with the message that warns about the download starting.After the Service Worker responds to a request for the download link it sends a message back to StreamSaver.js to notify about the download starting, as mentioned above.
mitm.html
For this to work on every scenario, a couple of changes were necessary.
Before this, the Service Worker was only registered after receiving the message to register a download. However, since the background page of Web Extensions on Firefox has no way to transfer ports to
mitm.html
directly, that would never happen.Therefore, we would need the Shared Worker to be started right away, so the background page doesn't have to wait for a random amount of time until it is. Since it is also in the Shared Worker that the "pinging" happens, I have altered the code to start the Service Worker right away regardless of any messages (in the meantime, messages are stored for later processing). Once the Service Worker is registered, only for the context of Web Extension's background pages the Shared Worker is also started (only for this context; the hash is what tells this is the case).
This also has the benefit of only trying to register/recover the Service Worker once, insted of once per download.
The only other change is on the
setInterval
time: now it is 29 seconds if on Firefox, and 4.5 minutes everywhere else.ping.html
This is a new file created to be a bridge to the Shared Worker, mainly to support "pinging" the Service Worker from unsafe contexts or different processes.
It is a VERY simple code that only starts the Shared Worker and forwards any message it receives to it.
ping.js
This is a new file created to be the code of the Shared Worker.
It serves two purposes:
Changes to functionality
Here I shall try to explain how the workings of the library were changed and why.
Any context not listed here should keep the previous functionality unchanged.
Download from
<iframe>
Any place where the download was being started by navigating to the download link is now changed to load the download link inside the
<iframe>
and remove the<iframe>
after the download starts.This was done because navigating away from the page closed all open connections, and that sometimes affects other code not from StreamSaver.js.
Pinging on Chrome/Opera
The pinging was happening every 29 seconds for Firefox compatibility, but now StreamSaver.js pings every 29 seconds only on Firefox, restoring the 4.5 minutes ping on Chrome/Opera.
Web Extension's background page on Chrome/Opera
The download was started with a popup created with the
chrome.tabs.create
function.Now it is started with
<iframe>
.This means no popups are ever opened on this context.
HTTP pages on Firefox
The Service Worker was getting killed after about 30 seconds, since nothing was keeping it alive.
Now, the Shared Worker is used to keep it alive for as long as necessary.
Web Extensions on Firefox
Web Extensions on Firefox were not supported before, but now they are.
Web Extension's pages loaded on tabs are capable of starting downloads without opening a single popup, but background pages need to open two popups per download.
No user interaction is required, though.
Examples for testing
I have created some example pages / Web Extensions to test all the changes in all contexts.
Each test has its own separate URL path for
mitm.html
andsw.js
, so you can test them all simultaneously without one affecting the other.Web Extension's background page download
This is a simple Web Extension. The only thing it does is start a download from the background page, transfering one
'a'
letter per second during the course of one hour.Download the extension and extract it: https://texkiller.eu.org/StreamSaver/compatibility-back.zip
How to test it
Firefox v65+
Chrome v52+
Opera v39+
about:debugging#addons
manifest.json
inside the extracted folderchrome://extensions/
opera://extensions/
Web Extension's page on a tab download
Another simple Web Extension. The only thing it does is open a page from the Web Extension into a tab with a button that, when pressed, starts a download transfering one
'a'
letter per second during the course of one hour.Download the extension and extract it: https://texkiller.eu.org/StreamSaver/compatibility-tab.zip
How to test it
Firefox v65+
Chrome v52+
Opera v39+
about:debugging#addons
manifest.json
inside the extracted folderchrome://extensions/
opera://extensions/
Web page on HTTP
This is a simple web page on the HTTP protocol.
It shows a button that, when clicked, starts a download transfering one
'a'
letter per second during the course of one hour.How to test it
Firefox v65+
Chrome v52+
Opera v39+
http://texkiller.eu.org/StreamSaver/compatibility/http/
Web page on HTTPS
This is a simple web page on the HTTPS protocol.
It shows a button that, when clicked, starts a download transfering one
'a'
letter per second during the course of one hour.How to test it
Firefox v65+
Chrome v52+
Opera v39+
https://texkiller.eu.org/StreamSaver/compatibility/https/
Things to look forward to
Here I shall mention changes in browsers that could open up new alternatives to the currently selected.
Transferable Streams
Explanation: https://github.com/whatwg/streams/blob/master/transferable-streams-explainer.md
Transferable Streams are expected to ease some of the library aspects, such as:
A Chrome implementation seems to be underway, and should be on stable Chrome in one of the next versions.
I could find no bug on Bugzilla that addresses this matter specifically, though, so Firefox might take some time to get there.
Starting Service Worker from
moz-extension://*
1344561 bug on Bugzilla: https://bugzilla.mozilla.org/show_bug.cgi?id=1344561
With this bug fixed, it should be always better to start the Service Worker from the
<iframe>
instead of thechrome.tabs.create
function on Web Extensions on Firefox.This would mean background pages require one less popup there, and Web Extension's pages on tabs might be able to start the download by just navigating to the download URL.
The Shared Worker would be unnecessary on Web Extensions, with HTTP pages on Firefox being the only situation where it would be needed.