Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add URL.createObjectURL and URL.revokeObjectURL #1721

Open
felipemsantana opened this issue Jan 31, 2017 · 36 comments
Open

Add URL.createObjectURL and URL.revokeObjectURL #1721

felipemsantana opened this issue Jan 31, 2017 · 36 comments
Labels

Comments

@felipemsantana
Copy link

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

@unional
Copy link

unional commented Feb 24, 2017

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

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

@domenic
Copy link
Member

domenic commented Feb 24, 2017

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

@unional
Copy link

unional commented Feb 24, 2017

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);
}

@felipemsantana
Copy link
Author

felipemsantana commented Feb 24, 2017

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

@unional
Copy link

unional commented Feb 25, 2017

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
Copy link

unional commented Feb 25, 2017

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?

@brettz9
Copy link

brettz9 commented Feb 28, 2017

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.

@unional
Copy link

unional commented Mar 3, 2017

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
Copy link

unional commented Mar 3, 2017

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?

@domenic
Copy link
Member

domenic commented Mar 3, 2017

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.

@unional
Copy link

unional commented Mar 3, 2017

Ok, so what's need to implement that?

@brettz9
Copy link

brettz9 commented Mar 3, 2017

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

@stereobooster
Copy link

stereobooster commented Jun 2, 2017

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.

@domenic
Copy link
Member

domenic commented Jun 2, 2017

@stereobooster
Copy link

stereobooster commented Jun 3, 2017

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

@fredvollmer
Copy link

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

@domenic
Copy link
Member

domenic commented Mar 2, 2018

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

@dylanjha
Copy link

dylanjha commented May 8, 2018

@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})
}

@franciscolourenco
Copy link

@dylanjha I get TypeError: Cannot redefine property: createObjectURL

jsdom@11.11.0

@dylanjha
Copy link

dylanjha commented Jun 5, 2018

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

@franciscolourenco
Copy link

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

window.URL.createObjectURL = noOp

@XhmikosR
Copy link

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!

@GMartigny
Copy link

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.

@vdechef
Copy link

vdechef commented Aug 10, 2021

(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 dumbmatter/fakeIndexedDB#56 (comment)

@lucaswiman
Copy link

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

@nemanjam
Copy link

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;
  }),
});

@lucaswiman
Copy link

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

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

@nemanjam
Copy link

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

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

It works, thanks, I lost 3 days to mock Blob, Fileand createObjectURL in jsdom.

@NameFILIP
Copy link

NameFILIP commented Jul 1, 2022

Good thread with a couple of workarounds, but it is still unclear:
Is there a plan to implement createObjectURL inside of jsdom?

@domenic
Copy link
Member

domenic commented Jul 1, 2022

The plan is for you to send a pull request!

@geminiyellow
Copy link

geminiyellow commented Oct 24, 2022

still open from 2017, say hi from 2022.

TypeError: URL.revokeObjectURL is not a function

still here.

and jsdom-worker not for it.

@tkrotoff
Copy link

tkrotoff commented Mar 26, 2023

How to force Jest/jsdom to use Node.js URL implementation and thus createObjectURL, revokeObjectURL... (requires Node.js >= 16.7.0):

// FixJSDOMEnvironment.ts

import JSDOMEnvironment from 'jest-environment-jsdom';

// https://github.com/facebook/jest/blob/v29.4.3/website/versioned_docs/version-29.4/Configuration.md#testenvironment-string
export default class FixJSDOMEnvironment extends JSDOMEnvironment {
  constructor(...args: ConstructorParameters<typeof JSDOMEnvironment>) {
    super(...args);

    // FIXME https://github.com/jsdom/jsdom/issues/1721
    this.global.URL = URL;
    this.global.Blob = Blob;
  }
}
// jest.config.js

/** @type {import('jest').Config} */
const config = {
  testEnvironment: './FixJSDOMEnvironment.ts',

  ...
}

module.exports = config;

See also #1724 (comment), #3363 (comment)

@csantos1113
Copy link

csantos1113 commented Sep 7, 2023

@tkrotoff thanks for your suggestion - I'm having an issue when trying to run that code:

● Validation Error:

  Test environment ./FixJSDOMEnvironment.ts cannot be found. Make sure the testEnvironment configuration option points to an existing node module.

  Configuration Documentation:
  https://jestjs.io/docs/configuration

Did you run into the same issue?

UPDATE: nvm, my rootDir was pointing somewhere else so it couldn't resolve it. I fixed my issue being explicit:

require.resolve('./FixJSDOMEnvironment.ts')

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests