jsdom icon indicating copy to clipboard operation
jsdom copied to clipboard

Add URL.createObjectURL and URL.revokeObjectURL

Open felipemsantana opened this issue 7 years ago • 33 comments

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

felipemsantana avatar Jan 31 '17 02:01 felipemsantana

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

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

unional avatar Feb 24 '17 03:02 unional

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

domenic avatar Feb 24 '17 05:02 domenic

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 avatar Feb 24 '17 06:02 unional

@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.

felipemsantana avatar Feb 24 '17 16:02 felipemsantana

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?

unional avatar Feb 25 '17 05:02 unional

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?

unional avatar Feb 25 '17 06:02 unional

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.

brettz9 avatar Feb 28 '17 14:02 brettz9

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?

unional avatar Mar 03 '17 03:03 unional

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?

unional avatar Mar 03 '17 03:03 unional

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.

domenic avatar Mar 03 '17 03:03 domenic

Ok, so what's need to implement that?

unional avatar Mar 03 '17 03:03 unional

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).

brettz9 avatar Mar 03 '17 04:03 brettz9

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.

stereobooster avatar Jun 02 '17 21:06 stereobooster

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

domenic avatar Jun 02 '17 21:06 domenic

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: () => {} }
}

stereobooster avatar Jun 03 '17 08:06 stereobooster

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

fredvollmer avatar Mar 02 '18 15:03 fredvollmer

https://mobile.twitter.com/slicknet/status/782274190451671040

domenic avatar Mar 02 '18 17:03 domenic

@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 avatar May 08 '18 04:05 dylanjha

@dylanjha I get TypeError: Cannot redefine property: createObjectURL

[email protected]

franciscolourenco avatar Jun 05 '18 09:06 franciscolourenco

@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 avatar Jun 05 '18 22:06 dylanjha

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

window.URL.createObjectURL = noOp

franciscolourenco avatar Jun 06 '18 07:06 franciscolourenco

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!

XhmikosR avatar Jun 12 '18 05:06 XhmikosR

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

PetterRuud avatar Nov 13 '18 13:11 PetterRuud

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

"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

ryderdonahue avatar Nov 15 '18 23:11 ryderdonahue

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

sumegha26 avatar Aug 11 '19 06:08 sumegha26

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.

GMartigny avatar Feb 11 '20 13:02 GMartigny

(hope it can be usefull for someone ...) I use Jest with FakeIndexedDB for my app's unit tests, and I was stucked because I needed URL.createObjectURL to work in jsdom. I could not use noOp, otherwise my tests would be meaningless.

I was finally able to implement a working mock for URL.createObjectURL, by using stuff from eligrey/Blob.js.
See https://github.com/dumbmatter/fakeIndexedDB/issues/56#issuecomment-896353134

vdechef avatar Aug 10 '21 22:08 vdechef

jsdom-worker adds a working implementation of createObjectURL, along with support for web workers.

lucaswiman avatar Mar 25 '22 20:03 lucaswiman

jsdom-worker adds a working implementation of createObjectURL, along with support for web workers.

@lucaswiman do you have example code how to use it? I am trying to mock Blob and URL.createObjectURL but I get recursion. Here is my code:

import 'jsdom-worker';
import { Blob } from 'blob-polyfill';

global.Blob = Blob;

// TypeError: URL.createObjectURL is not a function
Object.defineProperty(window.URL, 'createObjectURL', {
  value: jest.fn().mockImplementation((file) => {
    let result = null;

    const code = `onmessage = e => postMessage(e.data)`;
    const worker = new Worker(URL.createObjectURL(new Blob([code])));
    worker.onmessage = (arg) => {
      result = arg;
    };
    worker.postMessage(file);

    return result;
  }),
});

nemanjam avatar Mar 27 '22 18:03 nemanjam

@nemanjam If you're using jest, then you need to add or update the jest configuration in package.json:

  "jest": {
    "setupFiles": [
      "jsdom-worker"
    ]
  }

lucaswiman avatar Mar 27 '22 21:03 lucaswiman