Blob images will not display in Chrome if revokeObjectURL is invoked - javascript

-----update------
I commented out the window.URL.revokeObjectURL( imgSrc ); and now the call works in all browsers. It seems like the url was being revoked too soon in Chrome. I would still be very curious to know why this is, and to know if there are any problems in anyone's opinion with the way I am handling revoking the URLs now. I now revoke the last URL as the next one is loaded with if (imgSrc) {window.URL.revokeObjectURL( imgSrc );} (imgSrc is now a global variable).
I have a web site which uses AJAX to call a CGI script, which outputs image/jpeg data, and the blob response is then set as the src of an image on the page using the createObjectURL function. At present it works in all browsers but Chrome.
In all other browsers the image is displayed, but in Chrome I get the message: Failed to load resource: the server responded with a status of 404 (Not Found) followed by a url prefixed by blob:.
I have tried using webkitURL instead, but this gives me the same error followed by webkitURL is deprecated.
This is the code of my AJAX call:
var filepath = "/babelia.cgi";
xmlhttp=new XMLHttpRequest();
xmlhttp.onload = function(oEvent) {
if (imgSrc) {window.URL.revokeObjectURL( imgSrc );}
var blob = xmlhttp.response;
if (endless) {
imgSrc = (window.URL ? URL : webkitURL).createObjectURL( blob );
document.getElementById("palette").src = imgSrc;
// window.URL.revokeObjectURL( imgSrc );
babel();
}
}
xmlhttp.open("POST",filepath,true);
xmlhttp.responseType = "blob";
xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
var info = "location=" + postdata;
if (!landscape) {info += "&flip=portrait";}
xmlhttp.send(info);
You can see the website here: https://babelia.libraryofbabel.info/slideshow.html

I had some issues understanding blobs as well, here are my 2 cents.
I recommend using https://github.com/eligrey/Blob.js for abstracting away browser differences when working with blobs.
When debugging chrome check out chrome://blob-internals/ for details. With the current version (47.0.2526.106) it shows the urls and the associated data or references to local files for those urls.
As soon as you revoke the url you basically tell the browser that you no longer need the data, which is good if you want to free memory, but bad if you still have references pointing to that url (e.g. an img tag).
Side note: reading your question made me solve of my own problem which was that I was seeing 404s in chrome after revoking because I still had a reference to a revoked url on an img tag.

Related

Chromium createObjectUrl from Image comes out as net::ERR_FILE_NOT_FOUND

I know that my code works because I have been using it in firefox. When I switched to chrome, this code snippet has stopped working due to chrome unable to read the url generated by URL.createObjectURL().
export const image_preview = () => {
$('.js-thumbnail').off()
$('.js-thumbnail').on('change', function(event) {
// add event to each file input
const img = $(this).siblings('img')
const url = URL.createObjectURL(event.target.files[0]) // url to user's image
img.attr('src', url)
URL.revokeObjectURL(url) // free the allocated object
})
}
The url itself is generated but chromium fails to load it in my image tag.
Some posts suggests to use webkitURL api instead of URL but that didn't work either. Do you know the cause? Is this also a problem in other browsers as well?
You need to wait for the image has loaded before revoking its URL.
Image resources begin to load in a microtask after we set their src (among other reasons to allow setting crossOrigin properties after we set the src), so when the browser will read the src change, the line URL.revokeObjectURL(url) will already have been called, and the blob:// URL already pointing to nowhere.
So simply do
const img = $(this).siblings('img')
const url = URL.createObjectURL(event.target.files[0]) // url to user's image
img.attr('src', url)
img.one('load', (e) => { URL.revokeObjectURL(url); });

Blob name issue with new tab in chrome and firefox [duplicate]

In my Vue app I receive a PDF as a blob, and want to display it using the browser's PDF viewer.
I convert it to a file, and generate an object url:
const blobFile = new File([blob], `my-file-name.pdf`, { type: 'application/pdf' })
this.invoiceUrl = window.URL.createObjectURL(blobFile)
Then I display it by setting that URL as the data attribute of an object element.
<object
:data="invoiceUrl"
type="application/pdf"
width="100%"
style="height: 100vh;">
</object>
The browser then displays the PDF using the PDF viewer. However, in Chrome, the file name that I provide (here, my-file-name.pdf) is not used: I see a hash in the title bar of the PDF viewer, and when I download the file using either 'right click -> Save as...' or the viewer's controls, it saves the file with the blob's hash (cda675a6-10af-42f3-aa68-8795aa8c377d or similar).
The viewer and file name work as I'd hoped in Firefox; it's only Chrome in which the file name is not used.
Is there any way, using native Javascript (including ES6, but no 3rd party dependencies other than Vue), to set the filename for a blob / object element in Chrome?
[edit] If it helps, the response has the following relevant headers:
Content-Type: application/pdf; charset=utf-8
Transfer-Encoding: chunked
Content-Disposition: attachment; filename*=utf-8''Invoice%2016246.pdf;
Content-Description: File Transfer
Content-Encoding: gzip
Chrome's extension seems to rely on the resource name set in the URI, i.e the file.ext in protocol://domain/path/file.ext.
So if your original URI contains that filename, the easiest might be to simply make your <object>'s data to the URI you fetched the pdf from directly, instead of going the Blob's way.
Now, there are cases it can't be done, and for these, there is a convoluted way, which might not work in future versions of Chrome, and probably not in other browsers, requiring to set up a Service Worker.
As we first said, Chrome parses the URI in search of a filename, so what we have to do, is to have an URI, with this filename, pointing to our blob:// URI.
To do so, we can use the Cache API, store our File as Request in there using our URL, and then retrieve that File from the Cache in the ServiceWorker.
Or in code,
From the main page
// register our ServiceWorker
navigator.serviceWorker.register('/sw.js')
.then(...
...
async function displayRenamedPDF(file, filename) {
// we use an hard-coded fake path
// to not interfere with legit requests
const reg_path = "/name-forcer/";
const url = reg_path + filename;
// store our File in the Cache
const store = await caches.open( "name-forcer" );
await store.put( url, new Response( file ) );
const frame = document.createElement( "iframe" );
frame.width = 400
frame.height = 500;
document.body.append( frame );
// makes the request to the File we just cached
frame.src = url;
// not needed anymore
frame.onload = (evt) => store.delete( url );
}
In the ServiceWorker sw.js
self.addEventListener('fetch', (event) => {
event.respondWith( (async () => {
const store = await caches.open("name-forcer");
const req = event.request;
const cached = await store.match( req );
return cached || fetch( req );
})() );
});
Live example (source)
Edit: This actually doesn't work in Chrome...
While it does set correctly the filename in the dialog, they seem to be unable to retrieve the file when saving it to the disk...
They don't seem to perform a Network request (and thus our SW isn't catching anything), and I don't really know where to look now.
Still this may be a good ground for future work on this.
And an other solution, I didn't took the time to check by myself, would be to run your own pdf viewer.
Mozilla has made its js based plugin pdf.js available, so from there we should be able to set the filename (even though once again I didn't dug there yet).
And as final note, Firefox is able to use the name property of a File Object a blobURI points to.
So even though it's not what OP asked for, in FF all it requires is
const file = new File([blob], filename);
const url = URL.createObjectURL(file);
object.data = url;
In Chrome, the filename is derived from the URL, so as long as you are using a blob URL, the short answer is "No, you cannot set the filename of a PDF object displayed in Chrome." You have no control over the UUID assigned to the blob URL and no way to override that as the name of the page using the object element. It is possible that inside the PDF a title is specified, and that will appear in the PDF viewer as the document name, but you still get the hash name when downloading.
This appears to be a security precaution, but I cannot say for sure.
Of course, if you have control over the URL, you can easily set the PDF filename by changing the URL.
I believe Kaiido's answer expresses, briefly, the best solution here:
"if your original URI contains that filename, the easiest might be to simply make your object's data to the URI you fetched the pdf from directly"
Especially for those coming from this similar question, it would have helped me to have more description of a specific implementation (working for pdfs) that allows the best user experience, especially when serving files that are generated on the fly.
The trick here is using a two-step process that perfectly mimics a normal link or button click. The client must (step 1) request the file be generated and stored server-side long enough for the client to (step 2) request the file itself. This requires you have some mechanism supporting unique identification of the file on disk or in a cache.
Without this process, the user will just see a blank tab while file-generation is in-progress and if it fails, then they'll just get the browser's ERR_TIMED_OUT page. Even if it succeeds, they'll have a hash in the title bar of the PDF viewer tab, and the save dialog will have the same hash as the suggested filename.
Here's the play-by-play to do better:
You can use an anchor tag or a button for the "download" or "view in browser" elements
Step 1 of 2 on the client: that element's click event can make a request for the file to be generated only (not transmitted).
Step 1 of 2 on the server: generate the file and hold on to it. Return only the filename to the client.
Step 2 of 2 on the client:
If viewing the file in the browser, use the filename returned from the generate request to then invoke window.open('view_file/<filename>?fileId=1'). That is the only way to indirectly control the name of the file as shown in the tab title and in any subsequent save dialog.
If downloading, just invoke window.open('download_file?fileId=1').
Step 2 of 2 on the server:
view_file(filename, fileId) handler just needs to serve the file using the fileId and ignore the filename parameter. In .NET, you can use a FileContentResult like File(bytes, contentType);
download_file(fileId) must set the filename via the Content-Disposition header as shown here. In .NET, that's return File(bytes, contentType, desiredFilename);
client-side download example:
download_link_clicked() {
// show spinner
ajaxGet(generate_file_url,
{},
(response) => {
// success!
// the server-side is responsible for setting the name
// of the file when it is being downloaded
window.open('download_file?fileId=1', "_blank");
// hide spinner
},
() => { // failure
// hide spinner
// proglem, notify pattern
},
null
);
client-side view example:
view_link_clicked() {
// show spinner
ajaxGet(generate_file_url,
{},
(response) => {
// success!
let filename = response.filename;
// simplest, reliable method I know of for controlling
// the filename of the PDF when viewed in the browser
window.open('view_file/'+filename+'?fileId=1')
// hide spinner
},
() => { // failure
// hide spinner
// proglem, notify pattern
},
null
);
I'm using the library pdf-lib, you can click here to learn more about the library.
I solved part of this problem by using api Document.setTitle("Some title text you want"),
Browser displayed my title correctly, but when click the download button, file name is still previous UUID. Perhaps there is other api in the library that allows you to modify download file name.

javascript canvas getImageData error [duplicate]

I'm loading a motion jpeg from third-party site, which I can trust. I'm trying to getImageData() but the browser (Chrome 23.0) complains that:
Unable to get image data from canvas because the canvas has been tainted by
cross-origin data.
There are some similar questions on SO, but they are using local file and I'm using third party media. My script runs on a shared server and I don't own the remote server.
I tried img.crossOrigin = 'Anonymous' or img.crossOrigin = '' (see this post on the Chromium blog about CORS), but it didn't help. Any idea on how can I getImageData on a canvas with cross-origin data? Thanks!
You cannot reset the crossOrigin flag once it is tainted, but if you know before hand what the image is you can convert it to a data url, see Drawing an image from a data URL to a canvas
But no, you cannot and should not be using getImageData() from external sources that don't support CORS
While the question is very old the problem remains and there is little on the web to solve it. I came up with a solution I want to share:
You can use the image (or video) without the crossorigin attribute set first and test if you can get a HEAD request thru to the same resource via AJAX. If that fails, you cannot use the resource. if it succeeds you can add the attribute and re-set the source of the image/video with a timestamp attached which reloads it.
This workaround allows you to show your resource to the user and simply hide some functions if CORS is not supported.
HTML:
<img id="testImage" src="path/to/image.png?_t=1234">
JavaScript:
var target = $("#testImage")[0];
currentSrcUrl = target.src.split("_t=").join("_t=1"); // add a leading 1 to the ts
$.ajax({
url: currentSrcUrl,
type:'HEAD',
withCredentials: true
})
.done(function() {
// things worked out, we can add the CORS attribute and reset the source
target.crossOrigin = "anonymous";
target.src = currentSrcUrl;
console.warn("Download enabled - CORS Headers present or not required");
/* show make-image-out-of-canvas-functions here */
})
.fail(function() {
console.warn("Download disabled - CORS Headers missing");
/* ... or hide make-image-out-of-canvas-functions here */
});
Tested and working in IE10+11 and current Chrome 31, FF25, Safari 6 (Desktop).
In IE10 and FF you might encounter a problem if and only if you try to access http-files from a https-script. I don't know about a workaround for that yet.
UPDATE Jan 2014:
The required CORS headers for this should be as follows (Apache config syntax):
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "referer, range, accept-encoding, x-requested-with"
the x-header is required for the ajax request only. It's not used by all but by most browsers as far as I can tell
Also worth noting that the CORS will apply if you are working locally regardless of if the resource is in the same directory as the index.html file you are working with. For me this mean the CORS problems disappeared when I uploaded it to my server, since that has a domain.
You can use base64 of the image on canvas,
While converting into base64 you can use a proxy URL (https://cors-anywhere.herokuapp.com/) before your image path to avoid cross-origin issue
check full details here
https://stackoverflow.com/a/44199382/5172571
var getDataUri = function (targetUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
var proxyUrl = 'https://cors-anywhere.herokuapp.com/';
xhr.open('GET', proxyUrl + targetUrl);
xhr.responseType = 'blob';
xhr.send();
};
getDataUri(path, function (base64) {
// base64 availlable here
})

tainted canvas, cross-origin data [duplicate]

I'm loading a motion jpeg from third-party site, which I can trust. I'm trying to getImageData() but the browser (Chrome 23.0) complains that:
Unable to get image data from canvas because the canvas has been tainted by
cross-origin data.
There are some similar questions on SO, but they are using local file and I'm using third party media. My script runs on a shared server and I don't own the remote server.
I tried img.crossOrigin = 'Anonymous' or img.crossOrigin = '' (see this post on the Chromium blog about CORS), but it didn't help. Any idea on how can I getImageData on a canvas with cross-origin data? Thanks!
You cannot reset the crossOrigin flag once it is tainted, but if you know before hand what the image is you can convert it to a data url, see Drawing an image from a data URL to a canvas
But no, you cannot and should not be using getImageData() from external sources that don't support CORS
While the question is very old the problem remains and there is little on the web to solve it. I came up with a solution I want to share:
You can use the image (or video) without the crossorigin attribute set first and test if you can get a HEAD request thru to the same resource via AJAX. If that fails, you cannot use the resource. if it succeeds you can add the attribute and re-set the source of the image/video with a timestamp attached which reloads it.
This workaround allows you to show your resource to the user and simply hide some functions if CORS is not supported.
HTML:
<img id="testImage" src="path/to/image.png?_t=1234">
JavaScript:
var target = $("#testImage")[0];
currentSrcUrl = target.src.split("_t=").join("_t=1"); // add a leading 1 to the ts
$.ajax({
url: currentSrcUrl,
type:'HEAD',
withCredentials: true
})
.done(function() {
// things worked out, we can add the CORS attribute and reset the source
target.crossOrigin = "anonymous";
target.src = currentSrcUrl;
console.warn("Download enabled - CORS Headers present or not required");
/* show make-image-out-of-canvas-functions here */
})
.fail(function() {
console.warn("Download disabled - CORS Headers missing");
/* ... or hide make-image-out-of-canvas-functions here */
});
Tested and working in IE10+11 and current Chrome 31, FF25, Safari 6 (Desktop).
In IE10 and FF you might encounter a problem if and only if you try to access http-files from a https-script. I don't know about a workaround for that yet.
UPDATE Jan 2014:
The required CORS headers for this should be as follows (Apache config syntax):
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Headers "referer, range, accept-encoding, x-requested-with"
the x-header is required for the ajax request only. It's not used by all but by most browsers as far as I can tell
Also worth noting that the CORS will apply if you are working locally regardless of if the resource is in the same directory as the index.html file you are working with. For me this mean the CORS problems disappeared when I uploaded it to my server, since that has a domain.
You can use base64 of the image on canvas,
While converting into base64 you can use a proxy URL (https://cors-anywhere.herokuapp.com/) before your image path to avoid cross-origin issue
check full details here
https://stackoverflow.com/a/44199382/5172571
var getDataUri = function (targetUrl, callback) {
var xhr = new XMLHttpRequest();
xhr.onload = function () {
var reader = new FileReader();
reader.onloadend = function () {
callback(reader.result);
};
reader.readAsDataURL(xhr.response);
};
var proxyUrl = 'https://cors-anywhere.herokuapp.com/';
xhr.open('GET', proxyUrl + targetUrl);
xhr.responseType = 'blob';
xhr.send();
};
getDataUri(path, function (base64) {
// base64 availlable here
})

Download blobs locally using Safari

I'm trying to find a cross browser way to store data locally in HTML5. I have generated a chunk of data in a Blob (see MDN). Now I want to move this Blob to the actual filesystem and save it locally. I've found the following ways to achieve this;
Use the <a download> attribute. This works only in Chrome currently.
Microsoft introduces a saveAs function in IE 10 which will achieve this.
Open the Blob URL in the browser and save it that way.
None of these seems to work in Safari though. While (1) works in Chrome, (2) in IE and (3) in Firefox no one works in Safari 6. The download attribute is not yet implemented and when trying to open a blob using the URL Safari complains that URLs starting with blob: are not valid URLs.
There is a good script that encapsulates (1) and (3) called FileSaver.js but that does not work using the latest Safari version.
Is there a way to save Blobs locally in a cross browser fashion?
FileSaver.js has beed updated recently and it works on IE10, Safari5+ etc.
See: https://github.com/eligrey/FileSaver.js/#supported-browsers
The file name sucks, but this works for me in Safari 8:
window.open('data:attachment/csv;charset=utf-8,' + encodeURI(csvString));
UPDATE: No longer working in Safari 9.x
The only solution that I have come up with is making a data: url instead. For me this looks like:
window.open("data:image/svg+xml," + encodeURIComponent(currentSVGString));
Here data is the array buffer data coming from response while making http rest call in js. This works in safari, however there might me some issue in filename as it comes to be untitled.
var binary = '';
var bytes = new Uint8Array(data);
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
var base64 = 'data:' + contentType + ';base64,' + window.btoa(binary);
var uri = encodeURI(base64);
var anchor = document.createElement('a');
document.body.appendChild(anchor);
anchor.href = uri;
anchor.download = fileName;
anchor.click();
document.body.removeChild(anchor);
Have you read this article? http://updates.html5rocks.com/2012/06/Don-t-Build-Blobs-Construct-Them
Relating to http://caniuse.com/#search=blob, blobs are possible to use in safari.
You should consturct a servlet which delivers the blob via standard http:// url, so you can avoid using blob: url. Just make a request to that url and build your blob.
Afterwards you can save it in your filesystem or local storage.
The download attribute is supported since ~safari 10.1, so currently this is the way to go.
This is the only thing that worked for me on safari.
var newWindow = window.open();
const blobPDF = await renderMapPDF(); // Your async stuff goes here
if (!newWindow) throw new Error('Window could not be opened.');
newWindow.location = URL.createObjectURL(blobPDF);

Categories

Resources