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

Implement [LegacyFactoryFunction] #213

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

ExE-Boss
Copy link
Contributor

@ExE-Boss ExE-Boss commented Apr 29, 2020

This implements [LegacyFactoryFunction].

Unlike Chromium’s implementation, which assumes that a single interface will only ever have at most one legacy factory function, this implementation makes it possible to define more than one legacy factory function per interface, as can be seen with test/cases/LegacyFactoryFunction.webidl.

While working on this, I’ve noticed that exports.new(globalObject) will unconditionally throw a TypeError if the interface is a legacy platform object, because wrapper is defined as const wrapper, but the Proxy creation code uses:

wrapper = new Proxy(wrapper, proxyHandler);

Depends on:

@domenic
Copy link
Member

domenic commented Apr 29, 2020

Unlike Chromium’s implementation, which assumes that a single interface will only ever have at most one legacy factory function, this implementation makes it possible to define more than one legacy factory function per interface, as can be seen with test/cases/LegacyFactoryFunction.webidl.

Let's remove that complexity. We can add a constraint to the Web IDL spec that prohibits more than one LegacyFactoryFunction instead.

@domenic
Copy link
Member

domenic commented Mar 7, 2021

The generated snapshots here seem overcomplicated, and it's not clear why you're modifying existing functionality (like new()).

I think the root of the problem is that you're trying to construct the thisArg. Instead, since it's a factory function, the impl class can do that. In other words, I'd expect the generated code

    const thisArgument = exports.new(globalObject, new.target);
    const result = Impl.legacyFactoryFunction.call(thisArgument, globalObject, ...args);
    return utils.tryWrapperForImpl(utils.isObject(result) ? result : thisArgument);

to instead be

    return utils.wrapperForImpl(Impl.legacyFactoryFunction(globalObject, ...args));

@ExE-Boss
Copy link
Contributor Author

ExE-Boss commented Mar 7, 2021

@domenic That won’t support the case when new.target !== activeFunctionObject.

@domenic
Copy link
Member

domenic commented Mar 7, 2021

Hmm. I see there are references to NewTarget in https://heycam.github.io/webidl/#legacy-factory-functions but I am unsure whether they are implemented, or if they are, whether they're necessary for web compatibility.

@ExE-Boss
Copy link
Contributor Author

ExE-Boss commented Mar 10, 2021

@domenic All browsers implement the NewTarget handling (web-platform-tests/wpt#27991), so it’s probably necessary for web compatibility.

@domenic
Copy link
Member

domenic commented Mar 20, 2021

@ExE-Boss want to rebase this and finish review and then we can do a new release?

@ExE-Boss ExE-Boss force-pushed the feat/ext-attr/legacy-factory-function branch from 877562d to 78b8796 Compare March 20, 2021 22:51
@ExE-Boss ExE-Boss changed the title Implement [LegacyFactoryFunction] Implement [LegacyFactoryFunction] Mar 20, 2021
@ExE-Boss ExE-Boss force-pushed the feat/ext-attr/legacy-factory-function branch 2 times, most recently from 069ac03 to 94841f4 Compare March 20, 2021 22:59
@ExE-Boss ExE-Boss force-pushed the feat/ext-attr/legacy-factory-function branch from 94841f4 to 4a7471e Compare March 20, 2021 23:01
@@ -421,6 +421,17 @@ It is often useful for implementation classes to inherit from each other, if the

However, it is not required! The wrapper classes will have a correct inheritance chain, regardless of the implementation class inheritance chain. Just make sure that, either via inheritance or manual implementation, you implement all of the expected operations and attributes.

### The `[LegacyFactoryFunction]` extended attribute

For interfaces which have the `[LegacyFactoryFunction]` extended attribute, the implementation class file must contain the `legacyFactoryFunction` export, with the signature `(thisValue, globalObject, [...legacyFactoryFunctionArgs], { newTarget, wrapper })`, which is used for:
Copy link
Contributor Author

@ExE-Boss ExE-Boss Mar 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@domenic

I decided to pass the thisValue as the first parameter, rather than this so that it’s possible to use arrow functions for this:

exports.legacyFactoryFunction = (thisValue, globalObject, [...args], { newTarget, wrapper }) => {
	// code
}

I also decided to include the new.target and wrapper in a privateData object to match constructor(globalObject, args, privateData).

Copy link
Member

@domenic domenic Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would jsdom's implementations of the relevant parts of the HTML spec actually use any of thisValue, newTarget, or wrapper? If not, let's exclude them.

Copy link
Contributor Author

@ExE-Boss ExE-Boss Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should at the very least include thisValue and pass the args as an array to leave open the possibility of a privateData bag in case https://html.spec.whatwg.org/multipage/media.html#dom-audio, https://html.spec.whatwg.org/multipage/embedded-content.html#dom-image and https://html.spec.whatwg.org/multipage/form-elements.html#dom-option are rewritten.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still feeling like this is over-complex given that there are literally three cases and we know exactly how they behave. Our goal here is not to have a general framework to do any possible LegacyFactoryFunction; it's to implement Option, Audio, and Image.

In particular, I'd like to see these either support return override, or use the passed in thisValue, but not both. I suspect thisValue would be simpler, and perhaps necessary to support class X extends Audio {}. So I suspect the correct signature here will be (thisValue, globalObject, [...args]) with no support for return-override.

test/cases/OverloadedLegacyFactoryFunctionInterface.webidl Outdated Show resolved Hide resolved
@@ -0,0 +1,6 @@
// Simplified from https://html.spec.whatwg.org/multipage/form-elements.html#htmloptionelement
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What value does having these three tests give, instead of just one of them? I can see zero-args vs. nonzero, but these three cases don't seem to differ in interesting ways.

@@ -421,6 +421,17 @@ It is often useful for implementation classes to inherit from each other, if the

However, it is not required! The wrapper classes will have a correct inheritance chain, regardless of the implementation class inheritance chain. Just make sure that, either via inheritance or manual implementation, you implement all of the expected operations and attributes.

### The `[LegacyFactoryFunction]` extended attribute

For interfaces which have the `[LegacyFactoryFunction]` extended attribute, the implementation class file must contain the `legacyFactoryFunction` export, with the signature `(thisValue, globalObject, [...legacyFactoryFunctionArgs], { newTarget, wrapper })`, which is used for:
Copy link
Member

@domenic domenic Mar 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would jsdom's implementations of the relevant parts of the HTML spec actually use any of thisValue, newTarget, or wrapper? If not, let's exclude them.

`;

// This implements the WebIDL legacy factory function behavior, as well as support for overridding
// the return type, which is used by HTML's element legacy factory functions:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems more likely to be a historical artifact of how the spec is written?

It seems like we should be able to support either return overload (like HTML uses for its LegacyFactoryFunctions), or generate a this value (like modern Web IDL-using constructor specs do). We shouldn't need both, I don't think?

In particular given that you can subclass Option per the tests you wrote, I suspect the first line of https://html.spec.whatwg.org/#dom-option is just incorrect.

Copy link
Contributor Author

@ExE-Boss ExE-Boss Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurred to me that the order of Impl.legacyFactoryFunction and Impl.init calls is wrong, so I’m fixing that so that the object returned from Impl.legacyFactoryFunction is now the impl, and the result of makeWrapper is what’s returned from the LegacyFactoryFunction constructor.


This also makes it possible to support returning the result of new Impl.implementation.

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 this pull request may close these issues.

2 participants