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

Added HTML support for Button labels #1163

Merged
merged 22 commits into from
Sep 28, 2013
Merged

Added HTML support for Button labels #1163

merged 22 commits into from
Sep 28, 2013

Conversation

derek
Copy link
Contributor

@derek derek commented Sep 4, 2013

Currently Y.Button widgets do not support HTML labels. This is an issue because is isn't uncommon to use nested HTML structures inside of a <button> element. For example...

<button>
    <p>I Am A</p>
    <p>Button</p>
</button>

<button>
    <div>Image Button</div>
    <img src="path/to/image.jpg">
</button>

To fix this issue, this pull request introduces a labelHTML ATTR, and relegates the label ATTR as secure sugar around labelHTML.

A separate attribute is required for the following reasons:

  1. To not introduce security vulnerabilities to existing implementations who use the label ATTR
  2. To convey the insecurity of this attribute by including "HTML" in the name

Additional notes

  • Using @config instead of @attribute in ButtonCore documentation because @attribute will include the Change events to the API documentation, which ButtonCore (derived from AttributeCore) does not fire.
  • Fixed unrelated breakage in Selleck tests

Test Results

✓ Agent completed: Internet Explorer (9.0) / Windows 7
✓ Agent completed: Chrome (29.0.1547.65) / Mac OS
✓ Agent completed: Safari (6.0.5) / Mac OS
✓ Agent completed: Internet Explorer (10.0) / Windows 8
✓ Agent completed: Internet Explorer (8.0) / Windows XP
✓ Agent completed: Internet Explorer (6.0) / Windows XP
✓ Agent completed: Internet Explorer (7.0) / Windows XP
✓ Agent completed: Firefox (23.0) / Mac OS
✓ Agent completed: Safari (4.0) / Linux  (Android 2.3.4)
✓ Agent completed: Safari (4.0) / Linux  (Android 3.0.1)

✓ 352 tests passed! (1 minutes, 2 seconds)

@@ -68,7 +72,7 @@ Y.extend(Button, Y.Widget, {
*/
bindUI: function() {
var button = this;
button.after('labelChange', button._afterLabelChange);
button.after('labelHTMLChange', button._afterLabelHTMLChange);
Copy link
Member

Choose a reason for hiding this comment

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

Does this mean this change will break backwards compatibility?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

bindUI is actually being removed from Y.Button as I believe it is no longer necessary now with the proper setters in place.

FWIW, an intent of this pull request is to not break backwards compat in Button. I'm only introducing new features, as well as fixes to meet expected behavior.

@ericf
Copy link
Member

ericf commented Sep 4, 2013

What happens when the labelHTML is set on an <input type="submit" value="Save"> button node?

* IMPORTANT: Sanitize all input passed into this attribute
*
* @attribute labelHTML
* @type {HTML|String}
Copy link
Contributor

Choose a reason for hiding this comment

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

If labelHTML is always treated as HTML, then its type should be just HTML.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in next push

@derek
Copy link
Contributor Author

derek commented Sep 4, 2013

@ericf The provided text string is applied to the <input> element's value attribute and renders as a string.

So, with your provided HTML, executing this:

button.set('labelHTML', '<div>foo bar</div>');

Renders as

I just reviewed some of this behavior with @msweeney. He suggested some additional documentation to clarify that if you are dealing with an <input> element that you should only use label, but didn't see any issues to address.

* @method _uiSetLabel
* @method _getLabel
* @description Getter for a button's 'label' ATTR
* @return {String} The label for a given node
Copy link
Contributor

Choose a reason for hiding this comment

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

Since _getLabelFromNode() might return unsanitized HTML, this method must also be considered unsafe until _getLabelFromNode() is updated to always return something safe.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

…y removing most of the now unneccesary prototype properties.

* `Y.Button.ATTRS` now points to `Y.ButtonCore.ATTRS`
* Improved code documentation
@derek
Copy link
Contributor Author

derek commented Sep 5, 2013

As a result of an investigation into one of @rgrove's questions, I was able to fix the issue where _host was not set until Y.Button's initializer. Once that was fixed, it allowed Y.Button to use Y.ButtonCore's ATTRs, which allowed the removal of all the Y.Button-specific code to sync the ATTRs. As a result, Y.Button is now just a thin layer of Widget-specific properties which tie together methods available in Y.ButtonCore, which was originally the intended design for Y.Button.

P.S. All hidden diff comments have either been addressed, or are still a WIP.

* user provided content, appropriate sanitization steps should first be
* conducted to ensure the content is safe for insertion into the DOM.
* This process typically involves removing any &lt;script&gt; or
* &lt;iframe&gt; tags, or other HTML that can be considered malicious.
Copy link
Contributor

Choose a reason for hiding this comment

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

This isn't actually good advice, since it implies that blacklist-based tag filtering is a good approach, and also assumes that sanitization (rather than escaping) is the way to go, which is very rarely true in client-side JS.

This comment should just advocate escaping (using Y.Escape.html()) and leave it at that. Actual HTML sanitization is way out of scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To avoid clobbering the diff discussion with another commit, how about this instead?

/**
 * The HTML of the button's label
 *
 * **IMPORTANT**
 * This attribute accepts HTML and inserts it into the DOM **without**
 * sanitization.  This attribute should only be used with HTML that has
 * either been escaped (using `Y.Escape.html`), or sanitized according to
 * the requirements of your application.
 *
 * If all you need is support for text labels, please use the `label`
 * attribute instead.
 *
 * @attribute labelHTML
 * @type HTML
 */

Copy link
Contributor

Choose a reason for hiding this comment

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

Looks good! Feel free to omit the "IMPORTANT" though. That might be taking it too far. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed update.

tagName = labelNode.get('tagName').toLowerCase(),
label = labelNode.get(tagName === 'input' ? 'value' : 'text');

label = Y.Escape.html(label);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rgrove This escape was added to address this comment, though due to this getter now only returning a String and not mixed HTML|String, I believe the escapement is no longer necessary. Can you confirm and I'll remove it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed, but happy to add it back if neccesary.

@derek
Copy link
Contributor Author

derek commented Sep 10, 2013

I reviewed all the outdated diffs and believe all questions/issues have been addressed. Let me know if there's anything I missed.

@rgrove
Copy link
Contributor

rgrove commented Sep 10, 2013

Looks good to me. Thanks @derek. 👍

* @public
*/
getNode: function() {
if (!this._host && this instanceof Y.Widget) {
Copy link
Member

Choose a reason for hiding this comment

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

instanceof will fail across YUI instances. Duck-typing would be more robust in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Rethinking that line, checking if this is an instance of Y.Widget is a little over-cautious. ButtonCore inherits from AttributeCore, so it will always have a get method, so falling into the condition can't result in a script error. If the instance is a Widget, get('boundingBox') will return the node, if not, it will return undefined, which was already the value of this._host going into the branch. Update coming.

@ericf
Copy link
Member

ericf commented Sep 27, 2013

@derek This should have tests for input elements, because there are many code branches on whether a node is an input.

What is the coverage for the button modules? And what browsers was this tested in?

@derek
Copy link
Contributor Author

derek commented Sep 27, 2013

What is the coverage for the button modules? And what browsers was this tested in?

Button has 100% coverage (S, B, F, & L) and tested browsers are noted in the PR description. But another round of testing will be done, assuming additional tests are added and code changes occur.

@ericf
Copy link
Member

ericf commented Sep 27, 2013

@derek I added line comments earlier today as well…

… the `disabled` ATTR instead of the node's attribute property.
@derek
Copy link
Contributor Author

derek commented Sep 27, 2013

After the additional commit

✓ Agent completed: Safari (6.0.5) / Mac OS
✓ Agent completed: Chrome (29.0.1547.76) / Mac OS
✓ Agent completed: Firefox (24.0) / Mac OS
✓ Agent completed: Safari (7.0) / iOS 7.0
✓ Agent completed: Internet Explorer (10.0) / Windows 8
✓ Agent completed: Internet Explorer (9.0) / Windows 7
✓ Agent completed: Internet Explorer (8.0) / Windows XP
✓ Agent completed: Internet Explorer (6.0) / Windows XP
✓ Agent completed: Internet Explorer (7.0) / Windows XP
✓ 423 tests passed! (14 seconds)

@derek
Copy link
Contributor Author

derek commented Sep 28, 2013

This should have tests for input elements, because there are many code branches on whether a node is an input.

There are existing tests for <input> elements to cover all code branches, but I'll look into adding some more to strengthen Button's suite of unit tests. I added a line item for it on Button's Wishlist.

The reason there are no unit tests addressing <input> behavior for this pull request is because this PR adds support for HTML labels, which <input> elements do not support.

@ericf ericf merged commit 77340c8 into yui:dev-3.x Sep 28, 2013
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.

3 participants