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

Shadow DOM v1 #7059

Merged
merged 7 commits into from
Jan 24, 2017
Merged

Shadow DOM v1 #7059

merged 7 commits into from
Jan 24, 2017

Conversation

oliverfernandez
Copy link
Contributor

Support for Shadow DOM v1 in AMP Shadow

To cover cases like Safari 10, where Shadow DOM is implemented but Custom Elements is not, native Shadow DOM is disabled because Custom Elements polyfill does not cover custom elements inside shadow roots

Related to #6893

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check your existing CLA data and verify that your email is set on your git commits.
  • If you signed the CLA as a corporation, please let us know the company's name.

@oliverfernandez
Copy link
Contributor Author

I signed it!

Company name: Marfeel Solutions SL

@googlebot
Copy link

CLAs look good, thanks!

}

function areNativeCustomElementsSupported() {
return isNative(win.document.registerElement) || isNative(win.customElements && win.customElements.define);
Copy link
Contributor

Choose a reason for hiding this comment

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

iOS never had registerElement, so we can skip isNative(registerElement).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

But if the polyfill is loaded before this class, document.registerElement will exist, right? That why we need to check that it exists and is browser native.

You are right that checking for Custom Elements v1 does not make sense.

*/
let shadowDomMethod;

let win = window;
Copy link
Contributor

Choose a reason for hiding this comment

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

Unfortunately we can't have window-dependent side effects in our source code. Further getShadowDomMethod().call(...) might have issues if resolved via win.Element because we also allow most of APIs to be called from friendly iframes.

What I'm thinking instead, maybe we could try to have a string var: shadowDomSupport. It could have values none, v0 and v1. This way we can set this value from tests and try different code paths.

In that case, isShadowDomSupported() {return shadowDomSupport != 'none'}. And we could switch getShadowDomMethod() to something like this:

createShadowRoot(host) {
  switch(getShadowDomSupport()) {
     case 'v0':
        return host.createShadowRoot();
     case 'v1':
        return host.attachShadow({...});
  }
  assert(...);
}

*/
export function isShadowDomSupported() {
if (shadowDomSupported === undefined) {
shadowDomSupported = !!getShadowDomMethod() && areNativeCustomElementsSupported();
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add TODO:

TODO: Remove native CE check once WebReflection/document-register-element#96 is fixed.

});
});
});
});
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: trailing new line.

*/
export function getShadowDomMethod() {
if (shadowDomMethod === undefined) {
shadowDomMethod = win.Element.prototype.createShadowRoot || win.Element.prototype.attachShadow;
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say let's pull the trigger and make attachShadow (v1) the preferred way if both available.

@dvoytenko
Copy link
Contributor

@oliverfernandez Thanks a lot! This looks great! Just a couple of comments.

oliverfernandez added 2 commits January 19, 2017 19:48
Gives preference to Shadow DOM v1 over v0
@oliverfernandez
Copy link
Contributor Author

@dvoytenko I did all the changes you suggested.

However, I think that we need to keep checking for native support of document.registerElement, to check that it has not been polyfilled

};

/**
* @type {string|undefined}
Copy link
Contributor

Choose a reason for hiding this comment

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

{ShadowDomVersion|undefined}

let shadowDomSupportedVersion;

/**
* @param {string|undefined} val
Copy link
Contributor

Choose a reason for hiding this comment

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

{ShadowDomVersion|undefined}


/**
* Returns the supported version of Shadow DOM spec.
* @param {function=} optional for testing
Copy link
Contributor

Choose a reason for hiding this comment

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

Name is missing for @param - the compiler errors out on this in travis. Also, this is element class, right? Maybe let's call it opt_elementClass instead?

}

function areNativeCustomElementsSupported() {
return isNative(self.document.registerElement);
Copy link
Contributor

Choose a reason for hiding this comment

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

No, I meant we should actually only do the other one: isNative(self.customElements && self.customElements.define). Unless I misunderstood. But basically on iOS Safari, isNative(self.document.registerElement) will always be false, right?

However, there's still a problem. For some reason the polyfill overrides the existing the native CustomElementRegistry and so, event isNative(self.customElements.define) will always be false :(. We are getting a good traction on fixing the original issue though, but I don't know ETA yet.

The only way I found to determine this correctly is by running this code:

isNative(self.Object.getOwnPropertyDescriptor(self, 'customElements').get)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, got it.

The only drawback then will be that old Chrome versions, which only support Custom Elements v0, won't use native Shadow DOM. Are we ok with this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point. We could certainly also check for registerElement. Could you please take a look if polyfill overrides it as well? If it's too much trouble, we can move with this version, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, will do.

By the way, does self.Object.getOwnPropertyDescriptor(self, 'customElements').get works for you in Chrome? I always get undefined

Copy link
Contributor

Choose a reason for hiding this comment

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

It's undefined because of the polyfill. It always overrides customElement with the direct value. The native implementation, on the opposite, provides a getter and it's defined. So, in theory, we could simply check whether getter is present on not - but that feels too rigid to me.

Either way it's too rigid, but this way could get us going long enough for the original polyfill issue to be fixed and we'll be able to remove isNative check completely :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've tested in several devices, and in Chrome 55 self.Object.getOwnPropertyDescriptor(self, 'customElements').get always returns undefined, no matter if the polyfill is present or not. However, in Canary or Safari Tech Preview it behaves as you described. It's like customElements implementation in Chrome 55 is not providing the getter.

In any case, since document.registerElement is not overridden by the polyfill, I'll fix this by checking both document.registerElement or self.Object.getOwnPropertyDescriptor(self, 'customElements').get.

Working on it right now

Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks a lot!

@dvoytenko
Copy link
Contributor

@oliverfernandez Cool. This is very close. I asked a couple more updates. A more serious one - is still an issue with native-vs-non-native detection. I think I found a way to detect that. See comments on that above. Thanks a lot!

oliverfernandez added 2 commits January 24, 2017 18:50
Checks again for CE v0 so we don't disable native Shadow DOM in browsers that don't support CE v1
@oliverfernandez
Copy link
Contributor Author

@dvoytenko All changes applied, but I think I'm going to need some help in order Closure Compiler to accept Element.prototype.attachShadow

if (isShadowDomSupported()) {
const shadowDomSupported = getShadowDomSupportedVersion();
if (shadowDomSupported == ShadowDomVersion.V1) {
return hostElement.attachShadow({mode: 'open'});
Copy link
Contributor

Choose a reason for hiding this comment

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

So, yeah, attachShadow looks to be absent is externs :(

Please try to add this to shadow_dom.js:

/**
 * @see https://www.w3.org/TR/shadow-dom/#idl-def-ShadowRootInit
 * @typedef {{
 *   mode: string
 * }}
 */
var ShadowRootInit;

/**
 * @see https://www.w3.org/TR/shadow-dom/#h-extensions-to-element-interface
 * @param {ShadowRootInit=} opt_init
 * @return {!ShadowRoot}
 */
Element.prototype.attachShadow = function(opt_init) {};

This should do it.

@dvoytenko
Copy link
Contributor

@oliverfernandez Thanks! All looks good. We just need, as you said, add attachShadow to Element interface in externs. I added a comment above how to do this. Thanks a lot! As soon as travis passes I should be all good to merge.

@oliverfernandez
Copy link
Contributor Author

@dvoytenko I think we got it :)

@dvoytenko dvoytenko merged commit e0c9c88 into ampproject:master Jan 24, 2017
@dvoytenko
Copy link
Contributor

Merged. Thanks a lot, @oliverfernandez! We also did it on the eve of Safari 10.1 release with native CE support :)

@oliverfernandez
Copy link
Contributor Author

@dvoytenko And I think it gets even better! iOS 10.3 beta has been released today too, and I think it includes the updated Safari too. I'm checking it

jridgewell pushed a commit to jridgewell/amphtml that referenced this pull request Jan 31, 2017
* Shadow DOM v1 API support

* Removed unintended code comment

* PR fixes
Gives preference to Shadow DOM v1 over v0

* Fixed lint errors

* Fixes to closure-compiler errors
Checks again for CE v0 so we don't disable native Shadow DOM in browsers that don't support CE v1

* Fix closure compiler error

* Added Shadow DOM v1 spec to Closure Compiler
mrjoro pushed a commit to mrjoro/amphtml that referenced this pull request Apr 28, 2017
* Shadow DOM v1 API support

* Removed unintended code comment

* PR fixes
Gives preference to Shadow DOM v1 over v0

* Fixed lint errors

* Fixes to closure-compiler errors
Checks again for CE v0 so we don't disable native Shadow DOM in browsers that don't support CE v1

* Fix closure compiler error

* Added Shadow DOM v1 spec to Closure Compiler
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.

None yet

3 participants