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

Drop .textContent IE8 polyfill and rewrite escaping tests against public API #11331

Merged

Conversation

jeremenichelli
Copy link
Contributor

I want to put some explanation about this test rewrite since I think the outcome might not be what was intended in #11299

escapeTextContentForBrowser is used in client/setTextContent.js only when textContent is not supported for DOM elements, which only happens in IE8 (caniuse.com reference http://caniuse.com/#feat=textcontent) a browser version that is no longer supported (https://reactjs.org/blog/2016/01/12/discontinuing-ie8-support.html).

To actually move this test to run over ReactDOM.render and React.createElement it would be necessary to mock up the entire global.document object, assign null to document.documentElement.textContent, require React packages and then test this util through React element rendering. If authors and contributors think this should be the way to go I can start working again on this approach.

Considering that this part of the code might not be running anywhere today in React web applications, my suggestion would be to not add this heavy lifting on the tests, reconsider modifying these tests by merging the changes I've made and actually start thinking about removing escapeTextContentForBrowser from the codebase.

Since the support was dropped almost two years ago and we should start considering removing IE8 code.

Looking forward to read others take on this :)

@syranide
Copy link
Contributor

syranide commented Oct 24, 2017

var testElement = document.createElement('div');
testElement.innerHTML = escapeTextContentForBrowser('&');
expect(testElement.innerHTML).toEqual('&');

Does not actually test escapeTextContentForBrowser for correct behavior. It would pass even if you just did testElement.innerHTML = '&';, which is clearly broken.

If you want to test this in-terms of the public API I'm guessing it would be a better idea to do something along the lines of

React.render(<div>{'&amp;'}</div>, target);
target.firstChild.textContent === '&amp;';

@jeremenichelli
Copy link
Contributor Author

@syranide true that, as I described above the function I'm trying to test only runs in IE8, which could mean it's not used at all today in production.

My best bet is to work again on this PR, mock up the global.document object to mimic the inexistance of the textContent property in elements.

@syranide
Copy link
Contributor

syranide commented Oct 25, 2017

@jeremenichelli I'm pretty sure it's used for SSR too, which I'm guessing would be the actual way to test this (my code doesn't actually test it I realize 😄). It should be used for SSR at least.

@jeremenichelli
Copy link
Contributor Author

Ah that's a good catch @syranide, I don't know how SSR works in React to be honest, but if it passes the ExecutionEnvironment.canUseDOM condition and 'textContent' in document.documentElementis false there too, then it will get called for sure.

I'm travelling to Buenos Aires for NodeConfAR now, so updates on this PR will have to wait til next week.

@gaearon
Copy link
Collaborator

gaearon commented Oct 26, 2017

If you're confident something is only used in IE8, let's change this PR to delete that instead.

@jeremenichelli
Copy link
Contributor Author

@gaearon I'm 100% sure this only applies in IE8 cases (which implemented innerText instead of textContent), here's a list of references which support the statement:

The only thing I need to know is if this condition passes while server side rendering:

if (!('textContent' in document.documentElement)) {

If other contributors or users make sure SSRR doesn't need this, I will go on that path for sure.

@gaearon
Copy link
Collaborator

gaearon commented Oct 31, 2017

The only thing I need to know is if this condition passes while server side rendering:

SSR doesn't need this.

@jeremenichelli
Copy link
Contributor Author

jeremenichelli commented Oct 31, 2017

After some investigation, I noticed that escapeTextContentForBrowser is being used by the ReactPartialRenderer module from the server section of the package.

Also it's used by quoteAttributeValueForBrowser, for server rendering apparently.

Ideally the new roadmap would be to delete the textContent check in the client side since it's only there for IE8 as clarified before. Move the code to the shared folder and rename it escapeText since it's not used in the browser and it isn't applied in the text content situation itself, this will make the use of the code more clear.

We can re-check if the server tests satisfies the approach for quoted attributes and open a new issue.

Let me know if you agree with this new direction.

@gaearon
Copy link
Collaborator

gaearon commented Nov 1, 2017

Sounds reasonable to me.

@jeremenichelli jeremenichelli force-pushed the escapeTextContentForBrowser-test-improve branch from dd7feec to a8785a5 Compare November 8, 2017 04:02
@jeremenichelli
Copy link
Contributor Author

Just updated this PR. I want to clarify the changes first.

  • I've renamed escapeTextContentForBrowser to escapeText since it's just a util used by quoteAttributeValueForBrowser which is only used for server rendering.
  • I've moved tests on ReactServerRendering-test.js related to attribute value escaping.
  • On quoteAttributeValueforBrowser-test.js I'm testing directly through ReactDOMServer API to check if attribute values are correctly escaped.

A final note is that while doing this I didn't noticed tests for quoteAttributeValueForBrowser file were taken by @andrevargas in the original thread, and I think it would be the right thing to let him know if it's OK to merge this (after review approval) or if he first wants to show his PR to the team; and if the team prefers to merge his that I'll be totally fine with it. People before code 💟

@syranide
Copy link
Contributor

syranide commented Nov 8, 2017

I've renamed escapeTextContentForBrowser to escapeText since it's just a util used by quoteAttributeValueForBrowser which is only used for server rendering.

IMHO, that seems like a bad idea. There are many different ways to escape text/strings for the DOM, this is just one of them. At best it's confusing, at worst someone may use it to escape something they shouldn't (CSS, etc). EDIT: Although since it's being used for quoteAttributeValueForBrowser, the name of this function should at least exclude Content from TextContent, because TextContent implies it's only safe to use for textContent (which does not inherently make it safe for quoted attribute values).

Also, I don't understand how it can only be used by quoteAttributeValueForBrowser, how does the server-side escape inline text if not with this function?

@gaearon I'm also not sure why the current implementation is there, we had a far simpler and faster one "a long time ago"?

Unrelated but the description of escapeHtml is hilariously bad, "Escape special characters in the given string of html.".

@andrevargas
Copy link

Hey, thanks for letting me know about this, @jeremenichelli! I haven't figured out how exactly to test quoteAttributeValueforBrowser-test.js until now, and that's why I haven't sent any PR yet.

I didn't realize I could use ReactDOMServer API. Now I've taken a look at your PR and your tests for quoteAttributeValueForBrowser seem to be very reasonable.

It's awesome that you have done it! It's OK to merge this if approved, I'll be totally fine with it too! 👍
I just want to keep an eye on this PR, and let me know if I can help you with something. Again, thank you for caring about my efforts. :octocat:

@jeremenichelli
Copy link
Contributor Author

Thanks @andrevargas 💛

@syranide do you agree with changing the name to escapeStringForBrowser and improving description on escapeHtml method?

@gaearon
Copy link
Collaborator

gaearon commented Nov 13, 2017

@syranide Do you plan to continue reviewing this PR?

@jeremenichelli
Copy link
Contributor Author

@syranide @gaearon to proceed I need to know if we agree on escapeStringForBrowser name, improving escapeHtml description, and maybe re-add direct escapeStringForBrowser tests (no through APIs) or through ReactDOMServer.

@syranide
Copy link
Contributor

syranide commented Nov 13, 2017

@gaearon Sorry, no time this week (full-day business meetings) and I'm not really on-top of your internal guidelines well enough to suggest something more than what I've already done I feel. My intention was just to add what I know/felt was missing seeing as I've touched this code before.

@syranide do you agree with changing the name to escapeStringForBrowser and improving description on escapeHtml method?

Sure. The implementation of escapeHtml seems outside the scope of this PR though (and the name itself is bad too, also no one will ever read that description 😄).

@jeremenichelli
Copy link
Contributor Author

@syranide well it's a small gardening, and it wouldn't harm the PR talk if we agree on one ahead of new commits. How about escapeHtmlEntities?

@gaearon
Copy link
Collaborator

gaearon commented Nov 13, 2017

I'm not really on-top of your internal guidelines well enough to suggest something more than what I've already done I feel

We're pretty separate from any internal guidelines by now. I fully trust your intuition here. :-)

That said totally understand if you can't find the time. I'll try to dig in next week.

@syranide
Copy link
Contributor

That said totally understand if you can't find the time. I'll try to dig in next week.

@gaearon Just unusually busy this week, should have time this weekend :)

it('should escape boolean to string', () => {
expect(quoteAttributeValueForBrowser(true)).toBe('"true"');
expect(quoteAttributeValueForBrowser(false)).toBe('"false"');
// TODO: can we express this test with only public API?
Copy link
Collaborator

Choose a reason for hiding this comment

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

We can. Just embed it here :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Lol, forgot to take it out.

expect(escapeTextContentForBrowser(false)).toBe('false');
});

it('should escape object to string', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Which of the new tests verifies this?

});

it('should escape number to string', () => {
expect(quoteAttributeValueForBrowser(42)).toBe('"42"');
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same question.

});

it('should escape string', () => {
var escaped = quoteAttributeValueForBrowser(
'<script type=\'\' src=""></script>',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please add a test where this is literally the argument value.

Copy link
Contributor Author

@jeremenichelli jeremenichelli Nov 20, 2017

Choose a reason for hiding this comment

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

@gaearon I'm thinking of creating tests for escapeText through the ReactDOMServer.renderToString API like these ones, do you think that it would make sense to write them? Also, if you do, shouldn't this test be placed there where the script would actually run?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm thinking of creating tests for escapeText thourgh the ReactDOMServer.renderToString API like these ones

Sounds goood.

Also, if you do, shouldn't this test be placed there where the script would actually run?

Not sure what you mean. We just want to verify <script> can't be injected as attribute value.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, will add test on both escapeText and quoteAttribute tests.

Copy link
Collaborator

@gaearon gaearon left a comment

Choose a reason for hiding this comment

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

See above comments: let's make sure there is a corresponding test for each case we used to test before.

@gaearon gaearon mentioned this pull request Nov 20, 2017
26 tasks

escaped = quoteAttributeValueForBrowser('&');
expect(escaped).toBe('"&amp;"');
it('number is escaped to string inside attributes', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@gaearon from here, tests for number, object and script tags on attributes have been added back. Above tests for text content in nodes contemplate these cases too, let me know if you ahve any more concerns or something that I might have missed.


escaped = quoteAttributeValueForBrowser('&');
expect(escaped).toBe('"&amp;"');
Copy link
Collaborator

Choose a reason for hiding this comment

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

So, the nice thing about these old tests is they didn't assert how we escape, only that we do escape. Which opens the door for changing that in the future.

Maybe it's not too important 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.

What I didn't like form this test is that thy are just testing a switch JavaScript statement by doing it directly from the module. These internal modules pass thourgh other checks and algorithms when applied, so someone could modify that code, prevent the script to actually escape the characters and test would still pass, something that won't happen now 😃

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe an ideal test would actually create a DOM node, put content into innerHTML and then assert that it only has a text node child with expected content?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, that could be one way. Though werid since this is used only on the server package, maybe adding cases from renderToStaticMarkup would be valuable.

Copy link
Collaborator

@gaearon gaearon Nov 25, 2017

Choose a reason for hiding this comment

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

Though werid since this is used only on the server package

Not sure what you mean. The end goal of rendering to a string is that the HTML shows up in somebody's browser :-) This is exactly what an integration test should verify: that what shows up in the browser would have been consistent with what we expect (as opposed to a dangerous script tag).

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 understood it better after writing my response but didn't want to edit it.

Yeah I guess that would mimic the text string from server to node conversion better than just testing the string. Let me know if you are planning on migrating to that. I might think about revisiting these tests in the future if necessary.

Copy link
Collaborator

Choose a reason for hiding this comment

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

If you'd like to send a PR for this, happy to review. I'm not 100% sure it's the right approach but it seems to make sense to me.

@gaearon gaearon changed the title Gardening and improvements on escapeTextContentForBrowser tests Drop .textContent IE8 polyfill and rewrite escaping tests against public API Nov 23, 2017
@gaearon gaearon merged commit cafe352 into facebook:master Nov 23, 2017
HeroProtagonist pushed a commit to HeroProtagonist/react that referenced this pull request Nov 26, 2017
…lic API (facebook#11331)

* Rename escapeText util. Test quoteAttributeValueForBrowser through ReactDOMServer API

* Fix lint errors

* Prettier reformatting

* Change syntax to prevent prettier escape doble quote

* Name and description gardening. Add tests for escapeTextForBrowser. Add missing tests

* Improve script tag as text content test

* Update escapeTextForBrowser-test.js

* Update quoteAttributeValueForBrowser-test.js

* Simplify tests

* Move utilities to server folder
raphamorim pushed a commit to raphamorim/react that referenced this pull request Nov 27, 2017
…lic API (facebook#11331)

* Rename escapeText util. Test quoteAttributeValueForBrowser through ReactDOMServer API

* Fix lint errors

* Prettier reformatting

* Change syntax to prevent prettier escape doble quote

* Name and description gardening. Add tests for escapeTextForBrowser. Add missing tests

* Improve script tag as text content test

* Update escapeTextForBrowser-test.js

* Update quoteAttributeValueForBrowser-test.js

* Simplify tests

* Move utilities to server folder
Ethan-Arrowood pushed a commit to Ethan-Arrowood/react that referenced this pull request Dec 8, 2017
…lic API (facebook#11331)

* Rename escapeText util. Test quoteAttributeValueForBrowser through ReactDOMServer API

* Fix lint errors

* Prettier reformatting

* Change syntax to prevent prettier escape doble quote

* Name and description gardening. Add tests for escapeTextForBrowser. Add missing tests

* Improve script tag as text content test

* Update escapeTextForBrowser-test.js

* Update quoteAttributeValueForBrowser-test.js

* Simplify tests

* Move utilities to server folder
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants