52

I'm trying to save my HTML file in Chrome when the user presses ctrl + s keys but Chrome is crashed.

(I want to download just the source code of my HTML file)

I read that it happens because my file is bigger than 1.99M..

In the first attempt (before I knew about the crashing in Chrome):

function download(filename, text) {
    var pom = document.createElement('a');
    pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    pom.setAttribute('download', filename);
    pom.click();
}

download('test.html', "<html>" + document.getElementsByTagName('html')[0].innerHTML + "</html>");

The second attempt, after I read about the crashing, I used blob:

function dataURItoBlob(dataURI) {
    var byteString = atob(dataURI.split(',')[1]);

    var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]

    var ab = new ArrayBuffer(byteString.length);
    var ia = new Uint8Array(ab);
    for (var i = 0; i < byteString.length; i++) {
        ia[i] = byteString.charCodeAt(i);
    }

    var bb = new BlobBuilder();
    bb.append(ab);
    return bb.getBlob(mimeString);
}

function download(dataURI) {
    var blob = dataURItoBlob(dataURI);
    var url  = window.URL.createObjectURL(blob);
    window.location.assign(url);
}

download("<html>" + document.getElementsByTagName('html')[0].innerHTML + "</html>")

Here I got the error: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

I don't know, but I read that I need to encode my string to base64: How can you encode a string to Base64 in JavaScript?

There is an answer of 148 votes. I paste it in my code and don't know how to continue.

Where should I call it and how? Can I put a name on my saved file?

I think that I need to do something like:

download(_utf8_decode("<html>" + document.getElementsByTagName('html')[0].innerHTML + "</html>"))
Community
  • 1
  • 1
Alon Shmiel
  • 6,753
  • 23
  • 90
  • 138
  • What exactly are you trying to achieve? – TimWolla Mar 25 '14 at 21:22
  • I have an html file.. there are inputs that can be inserted dynamically.. (and the user can write text in these inputs).. after he finishes writing the text, I want to save the page with his changes, in order to let him continue in the point he left.. – Alon Shmiel Mar 25 '14 at 21:25
  • 2
    Why don't you save only the inputs and then embed them on the next page? – Shomz Mar 25 '14 at 21:27
  • I didn't understand you.. the user will be able to continue in the point he left? if he can, suggest me please.. assuming I have: http://jsfiddle.net/jaredwilli/tZPg4/4/ and I add 3 inputs.. how can I let the user close the file and then continue in the point he left (there are 3 more inputs).. (assuming I have only html file with big data.. 4 mb) – Alon Shmiel Mar 25 '14 at 21:32
  • Try to find (in your code) the redirection path, and put your eyes on it, maybe in your function download()... searching a litle on your code, look at URI definition! that`s happend on both environment? or only on production when you are trying to deploy? – Ignacio Trezza Aug 02 '19 at 22:42
  • This isn't an answer to the question, and should instead be posted as a comment on the original question. – phalteman Aug 02 '19 at 23:01

6 Answers6

32

BlobBuilder is obsolete, use Blob constructor instead:

URL.createObjectURL(new Blob([/*whatever content*/] , {type:'text/plain'}));

This returns a blob URL which you can then use in an anchor's href. You can also modify an anchor's download attribute to manipulate the file name:

<a href="/*assign url here*/" id="link" download="whatever.txt">download me</a>

Fiddled. If I recall correctly, there are arbitrary restrictions on trusted non-user initiated downloads; thus we'll stick with a link clicking which is seen as sufficiently user-initiated :)

Update: it's actually pretty trivial to save current document's html! Whenever our interactive link is clicked, we'll update its href with a relevant blob. After executing the click-bound event, that's the download URL that will be navigated to!

$('#link').on('click', function(e){
  this.href = URL.createObjectURL(
    new Blob([document.documentElement.outerHTML] , {type:'text/html'})
  );
});

Fiddled again.

Oleg
  • 24,465
  • 8
  • 61
  • 91
  • thank you for your comment.. unfortunately, it saves me the html before my changes.. (it doesn't save the text I entered).. I think you are so closer.. I didn't succeed to save the html before I saw your jsfiddle.. please help me again :) for example: http://jsfiddle.net/7NqKb/3/ please enter a text and press `download me` – Alon Shmiel Mar 26 '14 at 23:45
  • I tried: document.documentElement.outerHTML, document.documentElement.innerHTML, document.getElementsByTagName('html')[0].innerHTML.. but it doesn't remember the added inputs :/ – Alon Shmiel Mar 27 '14 at 00:07
  • 2
    @AlonShmiel: remember that when changing input text etc. you're not modifying html, you're modifying the DOM! You'll have to serialize DOM back into html/xhtml as `innerHTML`/`outerHTML` don't reflect mutations. – Oleg Mar 27 '14 at 01:17
  • 2
    @AlonShmiel - I think you can run something like `Array.prototype.forEach.call(document.querySelector('input'), function (e) { e.setAttribute('value', e.value); });` (and similar for `textarea` and `select`) before updating the download link. – PhistucK Apr 01 '14 at 20:00
10

Here I got the error: Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.

Because you didn't pass a base64-encoded string. Look at your functions: both download and dataURItoBlob do expect a data URI for some reason; you however are passing a plain html markup string to download in your example.

Not only is HTML invalid as base64, you are calling .split(',')[1] on it which will yield undefined - and "undefined" is not a valid base64-encoded string either.

I don't know, but I read that I need to encode my string to base64

That doesn't make much sense to me. You want to encode it somehow, only to decode it then?

What should I call and how?

Change the interface of your download function back to where it received the filename and text arguments.

Notice that the BlobBuilder does not only support appending whole strings (so you don't need to create those ArrayBuffer things), but also is deprecated in favor of the Blob constructor.

Can I put a name on my saved file?

Yes. Don't use the Blob constructor, but the File constructor.

function download(filename, text) {
    try {
        var file = new File([text], filename, {type:"text/plain"});
    } catch(e) {
        // when File constructor is not supported
        file = new Blob([text], {type:"text/plain"});
    }
    var url  = window.URL.createObjectURL(file);
    …
}

download('test.html', "<html>" + document.documentElement.innerHTML + "</html>");

See JavaScript blob filename without link on what to do with that object url, just setting the current location to it doesn't work.

Community
  • 1
  • 1
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • thank you.. I got: Failed to construct 'File': Illegal constructor I'm checking it.. – Alon Shmiel Mar 25 '14 at 21:54
  • 1
    I didn't test the code, sorry. Probably the `File` constructor is too new to be supported in (your) browser, [MDN doesn't mention it](https://developer.mozilla.org/en-US/docs/Web/API/File) which was why I linked the spec draft. Try using the `Blob` constructor (without filename however), which [is supported in all browsers](https://developer.mozilla.org/en-US/docs/Web/API/Blob#Browser_compatibility) – Bergi Mar 25 '14 at 22:01
  • it opened a page that his title is: `Blob:8db3e422-8597-4beb-....`, the content is all of my html source, and there is a message 'script is not responding'.. it wrote me it in the chrome and firefox.. – Alon Shmiel Mar 25 '14 at 22:15
7

you don't need to pass the entire encoded string to atob method, you need to split the encoded string and pass the required string to atob method

const token= "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJob3NzYW0iLCJUb2tlblR5cGUiOiJCZWFyZXIiLCJyb2xlIjoiQURNSU4iLCJpc0FkbWluIjp0cnVlLCJFbXBsb3llZUlkIjoxLCJleHAiOjE2MTI5NDA2NTksImlhdCI6MTYxMjkzNzA1OX0.8f0EeYbGyxt9hjggYW1vR5hMHFVXL4ZvjTA6XgCCAUnvacx_Dhbu1OGh8v5fCsCxXQnJ8iAIZDIgOAIeE55LUw"
console.log(atob(token.split(".")[1]));
Raparthi
  • 71
  • 1
  • 2
4

In my case, I was going nuts since there wasn't any issues with the string to be decoded, since I could successfully decode it on online tools. Until I found out that you first have to decodeURIComponent what you are decoding, like so:

atob(decodeURIComponent(dataToBeDecoded));
subharb
  • 3,374
  • 8
  • 41
  • 72
2

here's an updated fiddle where the user's input is saved in local storage automatically. each time the fiddle is re-run or the page is refreshed the previous state is restored. this way you do not need to prompt users to save, it just saves on it's own.

http://jsfiddle.net/tZPg4/9397/

stack overflow requires I include some code with a jsFiddle link so please ignore snippet:

localStorage.setItem(...)
Jeremy Danyow
  • 26,470
  • 12
  • 87
  • 133
  • I don't see how this answer the question. The OP does want to *download* the current html as a *file*, not store something in local storage? – Bergi Mar 25 '14 at 23:19
  • 1
    But it might be an alternative for what the OP is trying to accomplish as per the comments – Jorg Mar 25 '14 at 23:25
  • right- I think my answer is appropriate when you take this comment into consideration: _I want to save the page with his changes, in order to let him continue in the point he left._ – Jeremy Danyow Mar 26 '14 at 00:08
  • thank you for your comment.. I have a lot of inputs so your solution seems to me too hard.. but if I didn't find another solution, your solution will help me.. thank you for your comment!! :] – Alon Shmiel Mar 26 '14 at 23:49
  • I'm afraid my solution is the only one that's going to solve your issue: _saving the state of the inputs_ – Jeremy Danyow Mar 27 '14 at 18:25
0

I have the same error and what the fix I found was that in messaging.usePublicVapidKey('PAIR KEY') i was using the server key and it needs the "pair key". You could find that info in https://console.firebase.google.com/project/_/settings/cloudmessaging/

So the value of that function is the pair key in the console, i was confused and put the server key ‍♂️ and that create the bug of Uncaught DOMException: Failed to execute 'atob' on 'Window':.

Sanket Patil
  • 807
  • 1
  • 11
  • 19