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

Custom Elements: CustomElementRegistry #299

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions src/webapi/custom_elements.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use webcore::value::Reference;
webapi::error::TypeError;
Copy link
Owner

Choose a reason for hiding this comment

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

You're missing use here. (:


use webapi::dom_exception::{
NotSupportedError,
SyntaxError
};

#[cfg(feature = "experimental_features_which_may_break_on_minor_version_bumps")]
use webcore::promise::{Promise, TypedPromise};

/// The CustomElementRegistry interface provides methods for registering custom elements
/// and querying registered elements. To get an instance of it, use the window.customElements property.
///
/// [(JavaScript docs)](hhttps://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry)
/// https://html.spec.whatwg.org/multipage/custom-elements.html#customelementregistry
Copy link
Owner

Choose a reason for hiding this comment

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

Links to the specs should be in a normal comment, that is // instead of ///. (:

#[derive(Clone, Debug, PartialEq, Eq, ReferenceType)]
#[reference(instance_of = "CustomElementRegistry")]
pub struct CustomElementRegistry(Reference);


error_enum_boilerplate! {
/// A enum of the exceptions that CustomElementRegistry.define() may throw
DefineError,
Copy link
Owner

Choose a reason for hiding this comment

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

A little more verbose name would be better here, I think. Maybe CustomElementDefineError? It's a little long, but simply DefineError seems to generic.

/// A TypeError
TypeError
/// A NotSupportedError
NotSupportedError,
/// A SyntaxError
SyntaxError,
}

impl CustomElementRegistry {
/// Defines a new custom element
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/define)
/// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-define
pub fn define( &self, name: &str, constructor: Any) -> Result<(), DefineError> {
Copy link
Owner

@koute koute Nov 8, 2018

Choose a reason for hiding this comment

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

Hmmm.... we currently don't have a type which can represent a constructor. According to this a constructor can be detected with some tricks so I guess we technically could add one?

You'd define such a type using the usual #[derive(ReferenceType)], however you would omit the #[reference(instance_of = "...")] line and instead implement the InstanceOf trait yourself, and put the detection code inside of it. (You can take a look at e.g. the definition of the error_boilerplate! for an example on how to impl the InstanceOf) Then once we have this type you could use it here and in the return type of get.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the check for constructors is more involved, because custom elements require actual ES6 classes (and they must have the appropriate extends).

I think this also ties into the ability to define JS classes in stdweb, and we don't currently have a thorough plan for that.

I suppose we could create a simple struct Constructor(Reference) type for now (without any runtime checking).

Copy link
Owner

Choose a reason for hiding this comment

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

I think the check for constructors is more involved, because custom elements require actual ES6 classes (and they must have the appropriate extends).

Isn't the ES6 class syntax mostly a syntax sugar? I grabbed one of the examples off MDN of custom elements and I've modified the element definition to use the old-style of defining classes, and it looks like the example still works...? (At least in Firefox.) I wonder if that's just an accident? (Granted, I'm getting an InvalidStateError exception from somewhere once the control returns to the main loop, but the example still works, and my manual class-defining-voodoo might be slightly not what it expects, so, uh, I'm confused.)

Copy link
Author

Choose a reason for hiding this comment

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

Sorry, I haven't had the time to get back on this yet.

But, AFAIK, ES6 is not just sugar though. It can be done without ES6 syntax - using Object.setPrototypeOf, or Reflect.setPrototypeOf to be more compliant - however if I'm correct, these are reflection API in the JS engine, and are less performant than es6 syntax.

Copy link
Contributor

Choose a reason for hiding this comment

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

@koute No, it's not just sugar: the ability to extend host built-ins (like Array, Error, HTMLElement, etc.) doesn't work with ES5 classes, but it does with ES6 classes (which is why transpilers like Babel cannot extend those classes, since Babel compiles ES6 classes into ES5 classes).

As @prasannavl said, there are some ways to workaround it with ES5 classes, but it's not simple or easy (which is why almost nobody does it).

Could you post your code? When I tried using ES5 classes in Chrome it gave me errors when I called customElements.define.

Copy link
Owner

Choose a reason for hiding this comment

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

Could you post your code? When I tried using ES5 classes in Chrome it gave me errors when I called customElements.define.

I don't have in on hand anymore, however it really was just one of the examples picked at random with the class definition replaced with your garden variety old-style prototype-based JS class definition.

So this begs the question - is it actually possible to detect that something is a genuine ES6 class?

js!(
Copy link
Owner

Choose a reason for hiding this comment

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

You need to use js_try! here.

return @{self}.define(name, constructor);
).try_into().unwrap()
}

/// Returns the constuctor for the named custom element, or undefined if the custom element is not defined.
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/get)
/// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-get
pub fn get( &self, name: &str ) -> Option<Constructor> {
js!( return @{self}.get(name); ).try_into().unwrap()
}

/// Returns a promise that will be fulfilled when a custom element becomes defined with the given name.
/// (If such a custom element is already defined, the returned promise is immediately fulfilled.)
///
/// [(JavaScript docs)](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined)
/// https://html.spec.whatwg.org/multipage/custom-elements.html#dom-customelementregistry-whendefined
#[cfg(feature = "experimental_features_which_may_break_on_minor_version_bumps")]
pub fn whenDefined( &self, name: &str ) -> TypedPromise<(), SyntaxError > {
Copy link
Owner

Choose a reason for hiding this comment

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

This should be named in snake case, that is when_defined

let promise: Promise = js!( return @{self}.whenDefined(name); ).try_into().unwrap();
Copy link
Owner

Choose a reason for hiding this comment

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

Yes, this unwrap is fine. (As long as we have a guarantee that whenDefined will always return a Promise.)

TypedPromise::new(promise)
})
}