For IE11 in this code base64 file is converted to Blob, and then a download link is created. But with a large base64 file (~ >5Mb), the browser hangs at the moment when Blob is created:
new Blob(byteArrays, {type: contentType});
How is it possible to solve this problem?
var fullFileName = 'example.test';
var b64file = '';
var contentType = 'application/octet-stream';
b64toBlob(b64file, contentType, 512, function(blob){
if (typeof MouseEvent != "function") { //for IE
$('#ie_download').off('click').on('click', function(){
window.navigator.msSaveBlob(blob, fullFileName);
})
.show();
success();
return;
}
//other browsers
var blobUrl = URL.createObjectURL(blob);
var jqLink = $('<a style="display: none" target="_blank">Save</a>');
$('#download')
.attr('download', fullFileName)
.attr('href', blobUrl)
.show();
success();
});
function success () {
$('#waiting').hide();
}
function b64toBlob(b64Data, contentType, sliceSize, resultCb) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
var offset = 0;
setTimeout(function generateByteArrays () {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
offset += sliceSize;
if (offset < byteCharacters.length) {
setTimeout(generateByteArrays, 0);
}
else {
resultCb(new Blob(byteArrays, {type: contentType}));
}
}, 0);
}
#download, #ie_download {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id='waiting'>waiting...</div>
<a id='download'>Save</a>
<a id='ie_download'>IE Save</a>
Update
I just noticed that the segment size is 512 bytes. This is extremely small and will with a 5 mb file create 10,240 array slices which IE seem to do a very slow operation with (ie. create buffer, copy content, check next slice, create new buffer of old size and next slice's size, copy old buffer + new content etc.).
You should be able to use at least 1000x larger slice size (0.5 mb) and with that not block IE11.
Demo using original code with a larger slice size:
setTimeout(test, 10);
function test() {
// Generate a big base-64 string, chop off data-uri
// NOTE: Initial creation will take a couple of seconds...
var c = document.createElement("canvas"); c.width = c.height = 6000;
var ctx = c.getContext("2d"); // create some lines to degrade compression ratio...
for(var i = 0, r = Math.random.bind(Math), w = c.width, h = c.height; i < 500; i++) {
ctx.moveTo(r()*w, r()*h);ctx.lineTo(r()*w, r()*h);
}
ctx.stroke();
var base64 = c.toDataURL();
base64 = base64.substr(base64.indexOf(",")+1);
// OK, now we have a raw base64 string we can use to test
document.querySelector("out").innerHTML = "Converting...";
// Increase sliceSize by x1024
b64toBlob(base64, "application/octet-stream", 512<<10, function(blob) {
document.querySelector("out").innerHTML = "Blob size: " + blob.size;
});
function b64toBlob(b64Data, contentType, sliceSize, resultCb) {
contentType = contentType || '';
sliceSize = sliceSize || (512<<10);
var byteCharacters = atob(b64Data);
var byteArrays = [];
var offset = 0;
setTimeout(function generateByteArrays () {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
offset += sliceSize;
if (offset < byteCharacters.length) {
setTimeout(generateByteArrays, 5);
}
else {
resultCb(new Blob(byteArrays, {type: contentType}));
}
}, 5);
}
}
<out>Creating test data...</out>
Due to a bug in IE11 one cannot use XMLHttpRequest() with a data-uri and response type "blob", otherwise you could have used that to do all these operations for you.
var c = document.createElement("canvas"); c.width = c.height = 4000;
var ctx = c.getContext("2d"); // create some lines to degrade compression ratio...
for(var i = 0, r = Math.random.bind(Math), w = c.width, h = c.height; i < 200; i++) {
ctx.moveTo(r()*w, r()*h);ctx.lineTo(r()*w, r()*h);
}
ctx.stroke();
var base64 = c.toDataURL();
base64 = base64.substr(base64.indexOf(",")+1);
b64toBlob(base64, "application/octet-stream", function(blob) {
console.log(blob)
})
// Using XMLHttpRequest to do the work (won't work in IE11...)
function b64toBlob(base64, mimeType, callback) {
var xhr = new XMLHttpRequest();
xhr.responseType = "blob";
xhr.onload = function() {
callback(this.response)
};
xhr.open("GET", "data:" + mimeType + ";base64," + base64);
xhr.send();
}
Old answer (still applicable/recommended)
Increase the timeout to something like 7-10ms and see if that unblock the loop (or use even higher value if still blocking).
A timeout of 0 is in effect beating the purpose of this asynchronous segmentation approach.
Related
So I have some code that will take a blob and attempt to print it. This works fine the first time you click print in Safari. If you cancel the printing prompt, subsequent requests to print will show a white screen with infinite pages.
I was wondering if this is a core issue with safari, as this happens printing the document directly through JavaScript. Or if there's something I'm missing.
This only happens if you cancel a print. If you continue and finish printing, everything will work fine again until you click cancel on a print job initiated by JavaScript.
let printPdf = function (b64) {
const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => {
const byteCharacters = atob(b64Data);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize),
byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: contentType });
return blob;
}
const contentType = "application/pdf",
blob = b64toBlob(b64, contentType),
blobUrl = URL.createObjectURL(blob);
console.dir('new print');
var iframe = null;
iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.style.display = 'none';
iframe.onload = function() {
setTimeout(function() {
iframe.focus();
iframe.contentWindow.print();
}, 500);
};
iframe.src = blobUrl;
}
Here's a screenshot of the infinite printing window.
I'm making an offline PWA installer using Blob, but when I share and add to home screen on IOS, the PWA appears completely black. Here is my code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script>
function install(){
var autodate, enapp, data;
// Getting current PWA
$("#autodate").load("https://mysite.github.io/pwa/app1.html");
autodate = document.getElementById('autodate');
setTimeout(() => {
// Encode
encapp = btoa(autodate.innerHTML);
// Blob
blobt('text/html', 'data:text/html;base64,'+encapp)
}, 300);
};
function blobt(type, data){
const base64data = data;
const contentType = type;
const byteCharacters = atob(base64data.substr(`data:${contentType};base64,`.length));
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {type: contentType});
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl, '_blank');
}
</script>
I don't see what would make it not work. It does everything perfectly in-browser, but once you install it to home screen, it just blacks out. Am I doing something wrong? Is there a solution to this?
Thanks in advance.
This question already has an answer here:
Problems downloading big file(max 15 mb) on google chrome
(1 answer)
Closed 3 years ago.
function openNewWindow(strPreviewId) {
let newWindowViewer = window.open("");
var index = mapPreviewIdWithFile[strPreviewId].indexOf('base64,');
var attachBody = mapPreviewIdWithFile[strPreviewId].substring(index + 7);
var attachmentMimeType = mapPreviewIdWithFile[strPreviewId].substring(0, index + 7);
newWindowViewer.document.write("<iframe width='100%' height='100%' src='" + attachmentMimeType + " " + encodeURI(attachBody) + "'></iframe>");
}
finally the iframe gets created like below:
<iframe width="100%" height="100%" src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAA9AAAALaCAYAAADZQrs6AAAAAXNSR0IArs4..."></iframe>
For files like more than 10MB the attachBody changes to 26 MB, thus it fails to render in the new window. Is there any other library or workaorund which handles large files.
UPDATE
I was trying with canvas, but with canvas the full inage is bnot being shown, only a part is being shown.
if (attachmentMimeType.indexOf('bmp') > -1 || attachmentMimeType.indexOf('gif') > -1 || attachmentMimeType.indexOf('jpeg') > -1 ||
attachmentMimeType.indexOf('jpg') > -1 || attachmentMimeType.indexOf('pdf') > -1 || attachmentMimeType.indexOf('png') > -1) {
newWindowViewer.document.write('<canvas id="c"></canvas>');
var canvas = newWindowViewer.document.getElementById("c");
var ctx = canvas.getContext("2d");
var image = new Image();
image.onload = function() {
ctx.drawImage(image, 0, 0, image.height, image.width);
};
image.src = attachmentMimeType + " " + encodeURI(attachBody);
} else {
newWindowViewer.document.write("<iframe width='100%' height='100%' src='" + attachmentMimeType + " " + encodeURI(attachBody) + "'></iframe>");
}
The issue which you are facing is most probably because of the character length limit which is set for your brower. URLs over 2,000 characters will not work in the most popular web browsers. This SO answer explains it very well.
Alternatively you can try using IMG instead of IFRAME.
let attachBody = ""; // Add base64 string
let newWindowViewer = window.open("");
newWindowViewer.document.write("<img src=\""+ attachBody +"\" >");
Working demo fiddle here. Which is having a base64 encoded file(5.7M) opening properly in a new window.
Update 1
For pdf file you try by injecting blob data in a script tag. A sample script below.
printPreview = (data, type = 'application/pdf') => {
let blob = null;
blob = this.b64toBlob(data, type);
const blobURL = URL.createObjectURL(blob);
const newWindowViewer = window.open(blobURL);
const theDoc = newWindowViewer.document;
const theScript = theDoc.createElement('script');
function injectThis() {
window.print();
}
theScript.innerHTML = `window.onload = ${injectThis.toString()};`;
theDoc.body.appendChild(theScript);
};
b64toBlob = (content, contentType) => {
contentType = contentType || '';
const sliceSize = 512;
// method which converts base64 to binary
const byteCharacters = window.atob(content);
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
const slice = byteCharacters.slice(offset, offset + sliceSize);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, {
type: contentType
}); // statement which creates the blob
return blob;
};
However, I've not tested it.
I have used Fabricjs on my project. If I draw some thing to canvas it works fine at the time of convert them to dataurl. But when I draw any image and convert them to dataurl its not working properly.
Can anyone help on this.
Here is my code:
Load canvas from json:
CANVASOBJ.canvas.loadFromJSON(CANVASOBJ.canJson.items[k].data, CANVASOBJ.canvas.renderAll.bind(CANVASOBJ.canvas), function(o, object) {
});
Function to convert to blob:
var blob =CANVASOBJ.toBlob(imageFileType,CANVASOBJ.canvas);
toBlob : function (type,canvas) {
var dataN = canvas.toDataURL({
width: canvas.getWidth(),
height: canvas.getHeight(),
multiplier: ''
});
console.log(canvas.toDataURL("image/png"));
var data = dataN.replace("data:image/png;base64,", "");
return CANVASOBJ.b64toBlob(data, 'image/png');
},
b64toBlob:function (b64Data, contentType, sliceSize) {
contentType = contentType || '';
sliceSize = sliceSize || 512;
var byteCharacters = atob(b64Data);
var byteArrays = [];
for (var offset = 0; offset < byteCharacters.length; offset += sliceSize) {
var slice = byteCharacters.slice(offset, offset + sliceSize);
var byteNumbers = new Array(slice.length);
for (var i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
console.log(byteArrays);
var blob = new Blob(byteArrays, {type: contentType});
return blob;
}
I'm using flash to capture audio, encode it to mp3, then send it to javascript as ByteArray.
Now I want the javascript to save it as MP3 on my computer (not flash saves it to my computer). I am using Blob and then getDataURL, but the file isn't playing when saved. I used to do the same exact method to save WAV files and it worked perfectly fine.
Here's the JS code:
var getDataFromSWF = function (bytes) {
var myBlob = new Blob([bytes], { type: "audio/mpeg3" });
var url = (window.URL || window.webkitURL).createObjectURL(myBlob);
var link = window.document.createElement('a');
link.href = url;
// $("label").text(url);
link.download = 'output.mp3';
var click = document.createEvent("Event");
click.initEvent("click", true, true);
link.dispatchEvent(click);
// console.log(bytes);
}
I'm pretty much sure that the byteArray is fine because if I let the SWF save the file it works OK too. But I want to know what's wrong with the JS code. (note: i'm new to BLOB)
Try this to get the Blob
function base64toBlob(base64Data, contentType) {
var sliceSize = 1024;
var byteCharacters = atob(base64Data);
var bytesLength = byteCharacters.length;
var slicesCount = Math.ceil(bytesLength / sliceSize);
var byteArrays = new Array(slicesCount);
for (var sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) {
var begin = sliceIndex * sliceSize;
var end = Math.min(begin + sliceSize, bytesLength);
var bytes = new Array(end - begin);
for (var offset = begin, i = 0 ; offset < end; ++i, ++offset) {
bytes[i] = byteCharacters[offset].charCodeAt(0);
}
byteArrays[sliceIndex] = new Uint8Array(bytes);
}
return new Blob(byteArrays, { type: contentType });
}