Proper way to check if a URL is the result of a createObjectURL call?
Asked Answered
K

2

4

I have a situation where an image is being set by the user either to a URL or with bytes that is converted into a blob object URL. My understanding is that in order to prevent resource leaks, I should be freeing the blob object URLs when they are changed, but I'm not sure if I'm checking the type of the old URL correctly. Currently, the way I'm doing it is to just check if the url starts with 'blob:'. Here is a toy example function that demonstrates that it indeed seems to work:

var url;

for (i = 0; i < 5; i++) {
    var oldurl = url;
    console.log('i = ' + i)
    if (i == 0 || i == 2 || i == 3) {
        console.log('Switching to Object URL')
        url = URL.createObjectURL(new Blob(new Uint8Array(0),
                                           {type: 'image/png'}));
    } else {
        console.log('Switching to URL')
        url = 'https://example.com/example-image.png';
    }

    if (oldurl && oldurl.startsWith('blob:')) {
        console.log('Freeing old object url')
        URL.revokeObjectURL(oldurl);
    }
}

Is this the right way to do this? Is there a better way to do it?

I'll note that I've tried calling URL.revokeObjectURL on strings that are not object URLs and it seems to work fine, so it's also not clear to me how much it matters that I correctly determine whether the URL needs to be freed.

Note: This is a TypeScript script, but I think the question is equally valid in Javascript, so I've tagged with both.

Kanchenjunga answered 29/8, 2017 at 14:24 Comment(1)
Did you ever find another solution? I was thinking test if it includes blob:Gyre
F
0

You are right, i think that currently there is no other way at all.

Flanders answered 29/8, 2017 at 14:42 Comment(0)
G
0

Is this the right way to do this?

Yes.


In more detail:

Currently, the way I'm doing it is to just check if the url starts with 'blob:'.

Is this the right way to do this? Is there a better way to do it?

We can find out for sure by consulting the specification that defines how URL.revokeObjectURL(url) should work

(emphasis mine, and references to sub-instructions are inlined)

https://w3c.github.io/FileAPI/#dfn-revokeObjectURL

The revokeObjectURL(url) static method must run these steps:

  1. Let url record be the result of parsing url.
  2. If url record’s scheme is not "blob", return.
  3. Let origin be the origin of url record.
  4. Let settings be the current settings object.
  5. If origin is not same origin with settings’s origin, return.
  6. Remove an entry from the Blob URL Store for url:
    1. Let store be the user agent’s blob URL store;
    2. Let url string be the result of serializing url.
    3. Remove store url string.
      1. Remove all entries from the map that match a given condition (i.e. equal url)
      2. ...or do nothing if none do.

Additionally observe that the specification also notes that:

This means that rather than throwing some kind of error, attempting to revoke a URL that isn’t registered will silently fail. User agents might display a message on the error console if this happens.

Therefore, we can conclude:

  • If a URI's Scheme is blob:, or if a string URL starts with blob:, then it is a Blob URL.
    • ...and therefore it may be revoked by a script (on the same origin) calling URL.revokeObjectURL(url).
  • If URL.revokeObjectURL(url) is used with an invalid URL then nothing bad will happen (such as a thrown Error or other runtime exception). Examples of invalid URLs include:
    • non-blob: scheme URLs (due to step 2)
    • Different-origin URLs (due to step 5)
    • Already-revoked ObjectURLs (Due to step 6.3.2)
    • Syntactically valid, same-origin blob: URLs containing bogus UUIDs (Also due to step 6.3.2)

Addendum: A Regular-Expression for Matching blob: Object URLs:

The Web File API specification also prescribes a specific format for blob: URIs, which means they can be matched by a regular-expression RegExp too (assuming the specification doesn't change):

  1. Let result be the empty string.
  2. Append the string "blob:" to result.
  3. Let settings be the current settings object
  4. Let origin be settings’s origin.
  5. Let serialized be the ASCII serialization of origin.
  6. If serialized is "null", set it to an implementation-defined value.
  7. Append serialized to result.
  8. Append U+0024 SOLIDUS (/) to result.
  9. Generate a UUID RFC4122 as a string and append it to result.
  10. Return result
  • An example of a blob URL that can be generated by this algorithm is blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f64.

Therefore the RegExp in JS would be this (based on this RegExp to match HTTP Origin strings):

const blobObjectUrlRegex = /^blob:(?<origin>[\w\+]+:\/\/(?=.{1,254}(?::|$))(?:(?!\d|-)(?![a-z0-9\-]{1,62}-(?:\.|:|$))[a-z0-9\-]{1,63}\b(?!\.$)\.?)+(:\d+)?)\/(?<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;

function isBlobOrObjectUrl( url ) {
    return ( typeof url === 'string' ) && blobObjectUrlRegex.test( url );
}

Demo:

const objectUrlExample = 'blob:https://example.org/40a5fb5a-d56d-4a33-b4e2-0acf6a8e5f64';

const regex = /^blob:(?<origin>[\w\+]+:\/\/(?=.{1,254}(?::|$))(?:(?!\d|-)(?![a-z0-9\-]{1,62}-(?:\.|:|$))[a-z0-9\-]{1,63}\b(?!\.$)\.?)+(:\d+)?)\/(?<uuid>[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/;

console.log( "Is \"%s\" an ObjectURL (aka Blob URL)? %o", objectUrlExample, regex.test( objectUrlExample ) ? "Yes" : "No" );

const nonObjectUrlExample = 'https://mcmap.net/q/270603/-proper-way-to-check-if-a-url-is-the-result-of-a-createobjecturl-call';

console.log( "Is \"%s\" an ObjectURL (aka Blob URL)? %o", nonObjectUrlExample, regex.test( nonObjectUrlExample ) ? "Yes" : "No" );
Gazzo answered 26/3, 2023 at 13:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.