3

I'm traversing the DOM and one of the divs I analyze looks different than the rest. All other divs:

document.querySelector('#somediv') instanceof Node
// true

But one div:

document.querySelector('#strange_div') instanceof Node
// false

When I check the constructor of that div:

document.querySelector('#strange_div').constructor.name
// HTMLDivElement

And yet, when I test instanceof again:

document.querySelector('#strange_div') instanceof HTMLDivElement
// false

How is it possible ? A div that isn't an instance of Node, yet is an instance of HTMLDivElement

Note The div is of type ELEMENT_NODE

   document.querySelector('#strange_div').nodeType
   // 1

Checking the __proto__ chain

(7) [div#strange, HTMLDivElement, HTMLElement, Element, Node, EventTarget, {…}]
0: div#strange
1: HTMLDivElement {Symbol(Symbol.toStringTag): "HTMLDivElement", constructor: ƒ}
2: HTMLElement {…}
3: Element {…}
4: Node {…}
5: EventTarget {Symbol(Symbol.toStringTag): "EventTarget", addEventListener: ƒ, dispatchEvent: ƒ, removeEventListener: ƒ, constructor: ƒ}
6: {constructor: ƒ, __defineGetter__: ƒ, __defineSetter__: ƒ, hasOwnProperty: ƒ, __lookupGetter__: ƒ, …}

Comparing the Node in the proto chain:

document.querySelector('#strange').__proto__.__proto__.__proto__.__proto__ === Node
// false

Note 2

My intention here is to override the Node element with a specific getter function. This getter is available to all the divs but the one which isn't an instance of Node.

In more details: childNodes is extended by some 3rd party library and that's why it's unusable to me. I created a new iframe and I extend the Node element with a pure reference to the childNodes getter

const ref =
      Object.getOwnPropertyDescriptor(
        safeReferenceFromIframe.Node.prototype,
        functionName
      ) || {};
    Object.defineProperty(Node.prototype, `pure_${functionName}`, ref);
Yaron
  • 1,655
  • 6
  • 20
  • 38
  • 1
    look it - https://stackoverflow.com/questions/9979172/difference-between-node-object-and-element-object – s.kuznetsov Nov 22 '20 at 08:22
  • @sergeykuznetsov but if the div is of type HTMLDivElement wouldn't that make him also an instanceof Node, since it is extended from that? (that's what happens for the other elements) – Yaron Nov 22 '20 at 08:33
  • 1
    Can you share the relevant code from which that element is created and appended to the page? – David Thomas Nov 22 '20 at 08:38
  • 1
    Expecting `instanceof` to behave in any predictable manner is a bit of a fool's game imo. – JLRishe Nov 22 '20 at 08:41
  • @DavidsaysreinstateMonica I don't have access to it, but I can say it is done in angular. – Yaron Nov 22 '20 at 08:42
  • 2
    All that the value of the `constructor` property tells us is the value that that property currently has. It's a writable property. You could assign the value `"hello"` to it and it would retain that value. I'm not sure how one would create a functioning DOM node that's not an instance of `Node`, but things like `.constructor`, `instanceof` can be quite unreliable in telling the full story about an object. I would suggest inspecting the div's `__proto__` chain (in a debugger, not production code) to try and figure out how it came into being. – JLRishe Nov 22 '20 at 08:46
  • 1
    @JLRishe if ```instanceof``` and ```constructor``` are not reliable, I'm really stuck – Yaron Nov 22 '20 at 08:50
  • 2
    @Yaron Maybe you could explain what you're trying to do and what the actual problem is? That's the only way we can unstick you. Did you take my suggestion and look at the `__proto__` chain? – JLRishe Nov 22 '20 at 08:53
  • @JLRishe I updated the info in the question – Yaron Nov 22 '20 at 09:19
  • 1
    @Yaron Ok, well there's your problem - you're using an antipractice to troubleshoot an antipractice. You generally shouldn't be adding your own stuff to host objects unless it's for something like a polyfill. What is this new method intended to do? Are you trying to use this in production code, or just for fun? And what I meant by checking the `__proto__` chain was not to check just its immediate ancestor, but all of its ancestors. `var o = theDiv; var protos = []; do { protos.push(o); o = o.__proto__; } while (o); console.log(protos);` – JLRishe Nov 22 '20 at 09:30
  • @JLRishe I updated with the full ```__proto__ ``` chain. I wouldn't extend the Node normally, and indeed it is for a polyfill. – Yaron Nov 22 '20 at 09:44
  • 1
    @Yaron Ok, so the prototype chain shows `Node` in it. I would suggest comparing that to the global `Node` (`protos[4] === Node`) and seeing if they are equal. If not, it may be that the other `Node` is in some other frame or something like that and that is where your `div` is coming from (inheritance and `instanceof` don't work across frames). Still not sure what the solution would be if that's the case. What do you mean by "`childNodes` is sometimes overriden"? `childNodes` is supported since Chrome and Firefox 1 and IE 5. There should be no reason to override or polyfill it. – JLRishe Nov 22 '20 at 09:50
  • @JLRishe updated the info for your questions. – Yaron Nov 22 '20 at 10:26

1 Answers1

3

How is this possible?

There's no way for me to know exactly why it's happening in your case, but one well-known pitfall of instanceof is that it does not work across frames.

The Node in one frame is different from the Node in another frame. Your page's DOM can contain an element that was created in another frame, and if you use instanceof on it, it will not inherit from the Node in your page.

Like this:

const ifr = document.querySelector('iframe');

// Create an <a> element using an iframe's content document
const createdA = ifr.contentDocument.createElement('a');
createdA.textContent = "I am a link";
createdA.href = "http://www.yahoo.com";

// append it to the dom
document.body.appendChild(createdA);

// re-retrieve it
const myA = document.querySelector('a');

console.log('myA instanceof Node', myA instanceof Node); // false
console.log(myA.nodeName);                               // A

It sounds like you are working within a very chaotic environment - .childNodes accessors being overridden, mystery elements that don't inherit from Node.

My primary suggestion would be to find a way to remedy that.

If that's not an option, my next suggestion would be to abandon your attempts of modifying the Node prototype, and instead create a function that provides the functionality of the built-in childNodes property.

Looks like your current approach could be adapted in order to do that:

// Converts a prototype method to a function that takes an instance
// of that type as the first argument, followed by any additional arguments
const unbind = (f) => (that, ...args) => f.apply(that, args);   
    

const childNodesDesc =
    Object.getOwnPropertyDescriptor(
        ifr.contentWindow.Node.prototype,
        'childNodes'
    );
    
    
const childNodes = unbind(childNodesDesc.get);


// use childNodes
console.log(childNodes(strangeDiv));
JLRishe
  • 99,490
  • 19
  • 131
  • 169
  • Thanks for your very elaborate answer ! Does it follow that: ```strangeDiv.__proto__.__proto__.__proto__.__proto__ (Node)``` necessary equals one of the Node objects either on the main window, or in one of the iframes in the document ? – Yaron Nov 22 '20 at 15:43
  • 1
    @Yaron Probably, though it could also be from an `iframe` that was created and then removed. Or there may also be some other way of creating the situation you're seeing that I just haven't thought about. – JLRishe Nov 22 '20 at 18:59