Jsdom: Add URL.createObjectURL and URL.revokeObjectURL

Created on 31 Jan 2017  ·  26Comments  ·  Source: jsdom/jsdom

Hi, jsdom already supports the URL constructor, but not these 2 static methods, they are supported in most modern browsers.

createObjectURL
revokeObjectURL
Can I use

feature

Most helpful comment

@fredvollmer if using jest with jsdom and you want to noop this function you can add this code to setupTest.js (or an equivalent setup script)

function noOp () { }

if (typeof window.URL.createObjectURL === 'undefined') {
  Object.defineProperty(window.URL, 'createObjectURL', { value: noOp})
}

All 26 comments

Can the implementation in https://github.com/eligrey/Blob.js be used in jsdom?

I'm blocked by this for using jspdf inside jsdom.

No, the code there appears to create data URLs, not blob URLs. Someone needs to properly implement the spec at https://w3c.github.io/FileAPI/#dfn-createObjectURL

I'm not familiar with this, will it be something like this?

createObjectURL(blob) {
  var implementationDefinedValue = ???
  var url = `blob:${asciiSerialize(location.origin) || implementationDefinedValue}/${createUUID()}`;
  saveToBlobStore(url, blob);
  return url;
}
revokeObjectURL(blobUrl) {
  // assume `getFromBlobStore()` will not throw
  var blob = getFromBlobStore(blobUrl);

  if (!blob) {
    throw new NetworkError(...);
  }
  removeFromBlobStore(blobUrl);
}

@unional createUUID can be uuidV4

I think Chrome isn't following the spec strictly, URL.revokeObjectURL won't throw NetworkError if you pass the same args twice or any type not expected, like a number or array.

Edit:
The same happens with Firefox, to keep the implementation simple and like the browsers, I think it's not needed to have a blob store and URL.revokeObjectURL can be a noop function.

That means:

const uuid = require('uuid/v4');

createObjectURL(blob) {
  var implementationDefinedValue = ???
  var url = `blob:${asciiSerialize(location.origin) || implementationDefinedValue}/${uuid()}`;
  return url;
}
revokeObjectURL(blobUrl) {
  return;
}

asciiSerialize(origin) {
  if (origin.scheme) {
    return `${origin.scheme}://${serializeHost(origin.host)}${origin.port? ':' + +origin.port : ''}`;
  }
  else {
    return 'null';
  }
}

serializeHost(host) {
  ...
}

I guess there is a serializeHost() somewhere in the code already.

One last thing is:

If serialized is "null", set it to an implementation-defined value.

What does "implementation-defined value" mean?

Seems like I get all the questions answered.
http://stackoverflow.com/questions/42452543/what-does-implementation-defined-value-in-fileapi-unicodebloburl-mean/42452714#42452714

I think it is ready to be implemented at https://github.com/jsdom/whatwg-url

UPDATE: Turns out whatwg-url has most things available. So I guess the code is simply:

const uuid = require('uuid/v4');

createObjectURL(_blob) {
  var url = `blob:${serializeURL(location.origin)}/${uuid()}`;
  return url;
}
revokeObjectURL(_blobUrl) {
  return;
}

However I don't know how the whatwg-url is organized. So don't know how to put the code in.

@domenic , it the code above looks good and can be added there? Can you add it?

It ought to do more than just make the Blob URLs. It ought to subsequently allow a URL so generated to be provided to the likes of XMLHttpRequest.

It ought to do more than just make the Blob URLs. It ought to subsequently allow a URL so generated to be provided to the likes of XMLHttpRequest.

Does it need that? What is the use cases? Would the existing logic of XMLHttpRequest handle it just well?

I'm not sure if the goal of jsdom is to replicate the exact behavior of what a browser does.
If that is the case, we are writing a browser.

IMO we should defer this until there is an actual use case.

What else can we do to move this forward?

var url = blob:${serializeURL(location.origin)}/${uuid()};

The serializeURL() does a bit more than the algorithm described in the spec.
Should we use it or implement exactly as in the spec?

There's definitely no interest in adding object URLs to jsdom if they don't actually work (when used by XHR, img elements, etc.). Just creating them is very, very uninteresting. You can do that with a few lines of code; you don't need a whole jsdom implementation for that.

Ok, so what's need to implement that?

Besides allowing DOM APIs to be used in Node, It is very helpful to have such a browser for running automated tests.

But a specific use case for Node usage is that if you are running browser/Node code where a Blob gets created, this method can be used in both environments to allow you to introspect on the contents (as the browser at least otherwise does not allow this). (In my case, it would allow me to avoid writing Node-specific code for cloning Blobs in the structured cloning algorithm as used by IndexedDB (and postMessage).)

Blob URLs meet a special need of working in memory independent of a server. I think there are three unique aspects of Blob URLs over data: URLs (besides being able to compose them more easily without escaping and such).

One is that beyond in-memory content , they can reference files without needing to encode the whole file's contents within the URL (and not requiring it to be dereferenced until used).

Two is that they might be useful for security/privacy in avoiding persistence of the data beyond a browser session or outside of the user's browser.

Third is that Blob URLs can be revoked even within a session, allowing references to expire (e.g., if you create an image, display it via a Blob URL, and then the user deletes the image, you can revoke the URL so that the Blob URL cannot be reused, unlike would be the case with say a data URL).

There's definitely no interest in adding object URLs to jsdom if they don't actually work (when used by XHR, img elements, etc.). Just creating them is very, very uninteresting.

I want to do server side rendering of React (CRA) project with the help of react-snapshot. My project also uses react-mapbox-gl, which in turn uses mapbox-gl, which uses webworkify. And process fails because of

Error: Uncaught [TypeError: o.URL.createObjectURL is not a function]

I'm not interested in actually rendering map on server, I just want to render placeholder instead of map on server, but this error prevents it.

You can do that with a few lines of code; you don't need a whole jsdom implementation for that.

Is there a way I can patch jsdom to at least to add noop function for this? I suppose it will fail, but this at least will be starting point.

See the readme: https://github.com/tmpvar/jsdom#intervening-before-parsing

Thanks. It's a pity I'm stuck with old api e.g. jsdom.env, which doesn't have this

options.beforeParse(this[window]._globalProxy);

(facepalm) it will be a bit harder

UPD

there is created callback

created: (err, window) => {
  window.URL = { createObjectURL: () => {} }
}

What's the status of this feature request? It would certainly be useful to have.

@fredvollmer if using jest with jsdom and you want to noop this function you can add this code to setupTest.js (or an equivalent setup script)

function noOp () { }

if (typeof window.URL.createObjectURL === 'undefined') {
  Object.defineProperty(window.URL, 'createObjectURL', { value: noOp})
}

@dylanjha I get TypeError: Cannot redefine property: createObjectURL

[email protected]

@franciscolourenco you're right! Sorry about that... just updated the code in the above example to wrap in this conditional and that is working for me: if (typeof window.URL.createObjectURL === 'undefined') {

Give that a try!

Thanks for reminding me to come back here and update my example.

@dylanjha createObjectURL is defined but not a function, so some libraries throw errors. An assignment worked for me:

window.URL.createObjectURL = noOp

Does anybody know if this can be applied with gulp too? Basically I want to silence the jsdom errors about createObjectURL that come from jsdom which uncss is using.

Thanks in advance!

if (typeof URL !== 'undefined') {
  delete URL;
}

Just to make it extra clear, doing something like

function noOp () { }

if (typeof window.URL.createObjectURL === 'undefined') {
  Object.defineProperty(window.URL, 'createObjectURL', { value: noOp})
}

will only work if you put it within the "setupFiles" file of your jest configuration. So in your package.json or wherever you have
"jest": { "setupFiles": [ "<rootDir>/internals/testing/setup_file.js" ], }
you put that code written by others inside that file. (if that file doesnt exist you may need to create it)

Hope this helps anyone confused about the solution

img.src = URL.createObjectURL(blob);

not working, getting an error URL.createObjectURL is not a function while checking in jest test case file.
is there any alternative to assign returned blob to image src ? I am blocked

This is hacky and I would not recommend it, but I made a simplified implementation of createObjectURL and revokeObjectURL for Node.js.
Please make sure you know what you're doing before using it.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mitar picture mitar  ·  4Comments

kentmw picture kentmw  ·  3Comments

khalyomede picture khalyomede  ·  3Comments

Progyan1997 picture Progyan1997  ·  3Comments

potapovDim picture potapovDim  ·  4Comments