Skip to content
This repository has been archived by the owner on Apr 4, 2019. It is now read-only.

detect props that aren't writable, fixes emberjs/ember.js#11221 #353

Closed
wants to merge 1 commit into from

Conversation

jayphelps
Copy link
Contributor

Cc/ @stefanpenner

Working on tests, but want your input on the implementation while I do...

function isSettable(element, attrName) {
var desc = getOwnPropertyDescriptor(element.constructor.prototype, attrName);
if (!desc) { return true; }
if (!desc.writable || !desc.hasOwnProperty('value') && typeof desc.set !== 'function') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is the confusing and important part. We can change the "effective value" of a property (e.g. what we would get back if we accessed it) if it is writable OR it doesn't have a value AND has a set function. Browser spec guarantees that we will never get back a descriptor that has both a value and a set. Always mutually exclusive.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Property descriptors present in objects come in two main flavors: data descriptors and accessor descriptors. A data descriptor is a property that has a value, which may or may not be writable. An accessor descriptor is a property described by a getter-setter pair of functions. A descriptor must be one of these two flavors; it cannot be both. ~ Object.defineProperty

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note, while writing the tests, I just realized the first part !desc.writable isn't specific enough because of implicit boolean conversion of undefined. it needs to be desc.writable === false

@stefanpenner
Copy link
Collaborator

this may be better then: #352 but I wonder what examples beyond form require this functionality. I suspect in the world of custom WebComponents this may be the case, but i don't fully understand that scenario.

Can you share some further examples to help me understand this?

@jayphelps
Copy link
Contributor Author

@stefanpenner

Absolutely. While I wouldn't be surprised if there were more prop/attr pairs in the spec with this behavior, my major concern is indeed custom elements.

class XPerson extends HTMLElement {
  @readonly
  age = 33;
}

document.registerElement('x-person', XPerson);
<x-person age={{someAge}}></x-person>

When I write his out, it actually indeed makes me wonder if properties truly marked as readonly via writeable: false should throw an error (or let the browser naturally throw it by writing to it) while properties which simply don't have a setter would instead just write to the attribute.

I can see cases both ways...So I'm unsure.

@jayphelps
Copy link
Contributor Author

Summary: Do we need a distinction between "effectively readonly" and writeable: false?

AFAIK we can always setAttribute, but is there cases we this would be wrong and instead an error should be thrown? Because if someone has marked their prop as writeable: false, it may be very unintuitive that we set the attribute still.

@@ -2,6 +2,9 @@ export function isAttrRemovalValue(value) {
return value === null || value === undefined;
}

var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you confirm this works on IE8 (which we still have to support)?

https://msdn.microsoft.com/library/dd548686%28v=vs.94%29.aspx

As per that article, getOwnPropertyDescroptor does work in IE8 for "DOM Objects", so it may be perfectly fine, I just want to confirm.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Thinking more about this: we have IE8 in CI so if tests are green (and we are testing this path), we are good.

@stefanpenner
Copy link
Collaborator

the input[type] scenario doesn't appear to be handled by this.

questions is, should it? Or should it be an additional whitelist

@jayphelps
Copy link
Contributor Author

@stefanpenner If I'm following that thread correctly, IMO that's an unrelated case of "prop doesn't always reflect to attribute". The code for that might be in the same place, but unrelated concepts.

@jayphelps
Copy link
Contributor Author

Okay, I think I understand better now, the issue is actually the fact that is errors, not that it doesn't reflect. (still unrelated, but yeah, fix is in the same code path. I'm attempting a PR right now)

@stefanpenner
Copy link
Collaborator

Okay, I think I understand better now, the issue is actually the fact that is errors, not that it doesn't reflect. (still unrelated, but yeah, fix is in the same code path. I'm attempting a PR right now)

some funkyness for sure.
👍

@rwjblue
Copy link
Collaborator

rwjblue commented Jun 4, 2015

Can haz rebase?

}
}
propertyCaches[tagName] = cache;
}

// presumes that the attrName has been lowercased.
var value = cache[attrName];
var value = cache[attrName.toLowerCase()];
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 need @mixonic to chime in why before, this "presumed" attrName was lowercased, when in fact it never was (at least in my testing). From what I could tell, the cache always missed when the attrName !== attrName.toLowerCase(). Was that for some reason intended?

// assign anything. Both cases we will defer to setAttribute instead
var desc = getPropertyDescriptor(element, attrName);
if (!desc) { return true; }
if (desc.writable === false || !desc.hasOwnProperty('value') && typeof desc.set !== 'function') {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rwjblue and I are discussing this in Slack...but typeof desc.set !== 'function' fails in Safari due to this outstanding bug: https://bugs.webkit.org/show_bug.cgi?id=49739. tl;dr Safari is the only browser that doesn't return full descriptors for host element properties.

Chrome:
image

Safari:
image

@jayphelps
Copy link
Contributor Author

@rwjblue @stefanpenner @mixonic Given the discovery here #353 (comment), I've updated the solution to exclusively use a blacklist approach for all native elements. For Custom Elements, the robust solution is used for "ideal" interop. Whether or not setting the attribute when the property is not settable for Custom Elements is the right thing to do (vs. erroring or just not doing anything) is a bigger question of how third party web component libs like polymer, graffiti, etc expect things.

Please review and provide thoughts.

@jayphelps
Copy link
Contributor Author

To further complicate things...in some ways we're going down the same path that React does. Check this out: https://github.com/facebook/react/blob/4c3e9650ba6c9ea90956a08542d9fa9b5d72ee88/src/renderers/dom/shared/HTMLDOMPropertyConfig.js

@stefanpenner
Copy link
Collaborator

Find me at the meetup tonight. I believe we have a much better long term fix. Although this may still be a nice interim solution.

@Robdel12
Copy link

Any updates on this? :)

@jayphelps
Copy link
Contributor Author

@Robdel12 My latest understanding is that the fundamental way we choose whether we use the el.property = value vs setAttribute is likely to change. That said, @stefanpenner and I believed the issue would still exist for people choosing the wrong syntax (which doesn't exist yet) and we'd try and still fix it with this PR, but throw a warning of some kind.

I'm not the authority on this, just my understanding of it.

The idea is that <x-foo attr="{{bar}"> would use setAttribute because you put the mustache in quotes (HTML attributes are, after-all always strings) but if you omit the quotes <x-foo prop={{bar}> it would then use the same "props first if exists, setAttribute if not" pattern currently in place.

I'm personally in the camp of "this is going to confuse the hell out of people" because: previously we didn't really make it clear to devs that we were doing a props first, setAttribute second to begin with, most devs I've queried understandably don't realize there's a different between properties and attributes (I'm not joking), and maybe it won't be clear when to quote your 'staches and when not to. I'm super attached to this subject though, so I wouldn't be surprised if I was wrong about one or all of my concerns. I think it needs community input, likely an RFC. It's a tough problem because in almost every case we can choose props first and win, the dev doesn't need to know or care about the internals (which is why it was done) but these edge cases are brutal.

tl;dr

I hope this PR gets merged so people can move on, at least for now.

@jayphelps jayphelps changed the title [WIP] detect props that aren't writable, fixes emberjs/ember.js#11221 detect props that aren't writable, fixes emberjs/ember.js#11221 Jun 18, 2015
@Robdel12
Copy link

Thanks for the update! That helped me understand what's going on here.

jamesarosen referenced this pull request in jamesarosen/emberx-select Jun 28, 2015
@jamesarosen
Copy link

I don't know if it helps, but here's a JSBin that shows that

  1. setting form via prop doesn't take
  2. setting form via attr will cause <select>.form to be the new <form>
  3. setting form via attr will cause the form to include the <select> when it submits

@jayphelps
Copy link
Contributor Author

Superseded by #374

@jayphelps jayphelps closed this Jul 6, 2015
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants