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

All classes are shared between separate jsdom windows #1453

Closed
m4h3 opened this issue Apr 18, 2016 · 6 comments · Fixed by #2691
Closed

All classes are shared between separate jsdom windows #1453

m4h3 opened this issue Apr 18, 2016 · 6 comments · Fixed by #2691

Comments

@m4h3
Copy link

m4h3 commented Apr 18, 2016

defineProperty on DOM Interfaces seems to survive new page load (via jsdom.jdom()).

Object.defineProperty(HTMLAnchorElement.prototype, "href" , {configurable : false});

After new jsdom.jsdom() this should be true:
Object.getOwnPropertyDescriptor(HTMLAnchorElement.prototype, "href").configurable === true;

maybe/probably related to 1207.

@domenic
Copy link
Member

domenic commented Apr 18, 2016

This is intentional in the design of jsdom; we share a single DOM implementation across all jsdoms.

We have considered a mode where we generate new classes for every window, but it would be a lot of work.

@domenic domenic changed the title defineProperty on DOMInterfaces lifecycle All classes are shared between separate jsdom windows Apr 18, 2016
@m4h3
Copy link
Author

m4h3 commented Apr 18, 2016

Is there a "hack" so i can rebuild the DOM implementation ?

@domenic
Copy link
Member

domenic commented Apr 18, 2016

You can use separate node processes, each with a different jsdom instance.

@m4h3
Copy link
Author

m4h3 commented Apr 18, 2016

Unfortunately , I run mocha tests with jsdom.jsdom() in beforeEach() tests. I'm not sure to be able to launch separate processes for each test.

One way to do this "custom DOM implementation" without rebuilding the DOM would be to do something like this:
Object.defineProperty(window.Object,"defineProperty" ,{ value: magic_function }).
Object.defineProperty(window.Object,"getOwnPropertyDescriptor" ,{ value: magic_function2 }).

magic_function(target,attr,newconf) :

  1. register each "defineProperty" call from js sandbox in some ctx
  2. remember Object.getOwnPropertyDescriptor(target,attr) (aka oldconf) in this ctx
  3. if configurable = true in newconf => set to false but save this fact for magic_function2
  4. propagate and return native defineProperty

magic_function(targe,attr)

  1. recover ctx from magic_function
  2. call native getOwnPropertyDescriptor and return eventually modified configurable flag.

On jsdom.jsdom, revert all changes.

I guess that would solve my problem as test are not run in parallel, but it doesn't address the fact that All classes are shared.

Maybe ES6 Proxies are even better but I'm not enough familiar with them.

EDIT, this is close to what I tried to explain

@acusti
Copy link
Contributor

acusti commented Sep 27, 2016

I also ran into this issue recently while trying to write tests for a DOM utility to make sure that it would work across nested browsing contexts. I realized that the tests were always passing, even when the utility just used the global DOM interfaces rather than the interfaces on the nested browsing context’s window object.

I checked the HTML spec to try to figure out what the specific expected behaviors are for DOM interfaces. In the spec for creating browsing contexts, the steps specify to create new Window and Document objects, but don’t indicate whether that should include it’s own set of DOM interfaces, or if they can share the DOM interfaces of their parent browsing context. The documentation of instanceof on MDN talks about the fact that different browsing contexts have different execution environments, but doesn’t make any claims about correctness.

I tried these assertions in Chrome, Safari, and Firefox:

const iframe = document.createElement('iframe');
document.body.appendChild(iframe);

// Assertion 1 (DOM interface constructors are not the same actual variable)
console.assert(iframe.contentDocument.defaultView.Node !== Node, 'Iframe’s DOM interfaces are strict equals the same');
// Assertion 2 (DOM interface constructors have separate prototype chains)
console.assert(!(iframe.contentDocument.body instanceof Node), 'Iframe’s DOM interfaces has the global DOM interface in its prototype chain');
// Assertion 3 (Array constructor is not the same actual variable)
console.assert(iframe.contentDocument.defaultView.Array !== Array, 'Iframe’s Array constructor has the global Array constructor in its prototype chain');
// Assertion 4 (Array constructor has a separate prototype chain)
console.assert(!([] instanceof iframe.contentDocument.defaultView.Array), 'Iframe’s Array constructor has the global Array constructor in its prototype chain');

In Chrome and Safari, all of the assertions pass. In Firefox, assertion 2 fails, i.e. checking instanceof for the iframe's body element against the global Node constructor returns true, though I can’t figure out why.

Wrote a short test for jsdom also:

<!DOCTYPE HTML>
<title>Iframes get new and wholly separate execution contexts</title>
<link rel="help" href="https://www.w3.org/TR/html51/browsers.html#creating-a-new-browsing-context">

<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>

<script>
"use strict";

test(() => {
  const iframe = document.createElement('iframe');
  document.body.appendChild(iframe);
  assert_not_equals(iframe.contentDocument, document);
  assert_not_equals(iframe.contentDocument.defaultView, window);
  assert_equals(iframe.contentDocument.defaultView.Array === Array, false);
  assert_equals([] instanceof iframe.contentDocument.defaultView.Array, false);
  assert_equals(iframe.contentDocument.defaultView.Node === Node, false);
  assert_equals(iframe.contentDocument.body instanceof Node, false);

}, "iframe's nested browsing context has its own Document, Window, and DOM interface implementations");

</script>

As expected, the final two assertions fail. The rest of the assertions, including those checking the Array constructors, pass.

Having the ability to create new DOM implementations would be pretty great and help bring the environment closer to what you get in real browsers, though I also get the performance tradeoff. But FWIW, I would also put a vote in for a refactor to allow this.

@acusti
Copy link
Contributor

acusti commented Sep 28, 2016

To see a real-world example test I couldn’t write because of this limitation, check out facebook/fbjs#188 (comment)

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

Successfully merging a pull request may close this issue.

3 participants