Monkey patching constructors in builtin class hierarchies?

# /#!/JoePea (7 years ago)

Is it possible to monkey-patch an intermediate constructor of a built-in subclass?

For example, suppose I want all Element instances in a web app to have new instance properties, is there a way to monkey-patch the Element constructor so that when I make a custom element by extending a subclass of Element that the new logic will fire?

For example:

// monkey-patch the Element constructor somehow so that it logs "patched in
Element".

// then
class FooBar extends HTMLElement {}
customElement.define('foo-bar', FooBar)
new FooBar // "patched in Element"

I tried

const OldElement = window.Element

window.Element = function Element() {
  const _this = new OldElement
  console.log("patched in Element")
  return _this
}

window.Element.prototype = OldElement.prototype
window.Element.prototype.constructor = window.Element

class FooBar extends HTMLElement {}
customElements.define('f-b', FooBar)
new FooBar // does not log "patched in Element"

But when I make a new custom element, constructing it seems to use the old Element constructor, as if a non-global reference to the original constructor is kept inside a module so that modifying the global wouldn't have any effect.

Is there a way to monkey patch a constructor in the middle of a built-in prototype chain or to otherwise inject construction logic to base classes of existing class hierarchies?

# Boris Cherny (7 years ago)

This feels like a problem similar to esdiscuss.org/topic/block

# Andrea Giammarchi (7 years ago)

if that would be possible, then everyone could just monkey patch Object, right?

I'm not sure I'd be up for it

# Michał Wadas (7 years ago)

AFAIR DOM classes are not extensible by any means.

# Oriol _ (7 years ago)

The problem is that DOM defines the Element interface without a [Constructor] extended attribute.

This means that Element can't be directly constructed. However, you can still use something like

class FooBar extends Element {
  constructor() {
    console.log("patched in Element");
    let el = document.createElement('foo-bar');
    return Object.setPrototypeOf(el, FooBar.prototype);
  }
}
# /#!/JoePea (7 years ago)

This feels like a problem similar to esdiscuss.org/topic/block-scoped-prototype-extensions

@Boris, even if it were scoped, how do we monkey patch a constructor? By the way, for some reason your link to https://esdiscuss.org/topic/block-scoped-prototype-extensions posted as https://esdiscuss.org/topic/block which is 404. If you can edit it it would help others not to stumble on a broken link.

if that would be possible, then everyone could just monkey patch Object, right?

But everyone can monkey patch the entire class already, aside from the constructor, by modifying the prototype. Obviously if someone returns something new from the constructor they might break everything, but it will be completely obvious and people then won't do that. The same applies with methods and properties, it is super easy to break entire applications monkey patching methods.


So suppose I want to "polyfill" a concept. For example, I want all elements to have a new "foo" accessor after they've been constructed. Or for example, suppose HTMLElement.prototype.style and SVGElement.prototype.style didn't exist yet. How would I patch those in? /#!/JoePea

# /#!/JoePea (7 years ago)

Well, I know I can set accessors on a prototype easily, but what I mean is, I need each instance of an Element to have exactly one instance of something right after construction during the same synchronous operation.

For example, if we have an application with a bunch of pre-existing Elements that do not extend from any classes of mine, I'd like for the all to have foo properties, so that I can do this:

const el = new SomeCustomElementThatWasDefinedCreatedByMe
console.log(el.foo) // it exists and is specific to the instance, not
a prototype property

// or

const div = document.createElement('div')
console.log(div.foo) // it exists and is specific to the instance, not
a prototype property

Can this be done? /#!/JoePea

# Michał Wadas (7 years ago)

You can use getters mutating an object for this task.

But why do you need this?

# Logan Smyth (7 years ago)

Given that these are constructors that you don't own, adding your own properties to them seems like an overall ugly approach to me. Why not store your data separately in a WeakMap and rather than injecting properties onto existing objects? Then you can initialize the stored data on first access of the data you want.

# Andrea Giammarchi (7 years ago)

I know I'm going to regret this already, but since I've secretly played with polyfills, you can still do this:

window.HTMLElement = class extends HTMLElement {
  constructor() {
    super();
    console.log('hello darkness my old friend');
  }
};

// then ...
class MyEl extends HTMLElement {}
customElements.define('my-el', MyEl);

new MyEl;
# /#!/JoePea (7 years ago)

@Michał

But why do you need this?

To "polyfill" an idea I have for DOM, to propose it later, etc.

@Logan

Why not store your data separately in a WeakMap and rather than injecting properties onto existing objects? Then you can initialize the stored data on first access of the data you want.

I thought about that, it seems like the only way, but was curious to see about the the during-construction way if possible.

@Andrea

But this one doesn't work:

window.Element = class extends Element {
  constructor() {
    super();
    console.log('hello darkness my old friend');
  }
};

// then ...
class MyEl extends HTMLElement {}
customElements.define('my-el', MyEl);

new MyEl;

/#!/JoePea

# Boris Zbarsky (7 years ago)

On 10/24/17 12:50 PM, /#!/JoePea wrote:

Is it possible to monkey-patch an intermediate constructor of a built-in subclass?

Right now, no.

For example, suppose I want all Element instances in a web app to have new instance properties, is there a way to monkey-patch the Element constructor so that when I make a custom element by extending a subclass of Element that the new logic will fire?

If you want to limit this to custom elements you control, in the sense of html.spec.whatwg.org/multipage/custom-elements.html, then "sort of, yes". You could just define a class that extends HTMLElement, have its constructor do whatever instance-property-setting you want, and have all your actual custom element classes extend that one class.

If you want to do this to custom elements you do NOT control, you can sort of do it too, using Andrea's suggestion later in this thread: as long as your code runs first, you can set your class as window.HTMLElement so everyone else is extending you, not the "real" HTMLElement.

const OldElement = window.Element

This won't work, because Element is not constructible, so all the constructor behavior lives in HTMLElement. But if you did this with HTMLElement, I would expect it would work, as in Andrea's suggestion.

Is there a way to monkey patch a constructor in the middle of a built-in prototype chain or to otherwise inject construction logic to base classes of existing class hierarchies?

Right now, no.

# Michał Wadas (7 years ago)

To "polyfill" an idea I have for DOM, to propose it later, etc.

You can consider something like this depending on your needs: gist.github.com/Ginden/03004e52b3d331e236b5256e3b4c08ff

# /#!/JoePea (7 years ago)

I'm going to use the accessor technique as it's the only way. Thanks!

I was trying to patch Element because there's also SVGElement. I suppose I can duplicate efforts and patch both HTMLElement and SVGElement; that's not too difficult, but it is conceptually uglier because for example, what if browsers come out with MathElement, WebGLElement, etc. /#!/JoePea

# Andrea Giammarchi (7 years ago)

are you patching things you know need patching or you want to pollute forever the future with your patch? Whatever you are doing should not compromise WebGLElement so it's good you are unable to patch Element, IMO

# /#!/JoePea (7 years ago)

It's a generic thing to forever impact all future descendants of Element, in theory. For example, a feature like "Element Behaviors" as described here (but I'm going to close that one and open a more concise issue with a working implementation).

The idea is that it would work on any type of element because it is really generic (just like jQuery works on any type of element), so patching Element seemed the best way to do it. /#!/JoePea

# Boris Zbarsky (7 years ago)

On 10/24/17 2:03 PM, /#!/JoePea wrote:

I was trying to patch Element because there's also SVGElement

For what it's worth, looks like for the moment custom elements has given up on the idea of custom non-HTML elements...

# /#!/JoePea (7 years ago)

I'm not polyfilling Custom Elements, I'm polyfilling something else that doesn't have the problems that Custom Elements has that makes them not usable with SVG. /#!/JoePea