@samitouri / QOS-React-2 / commits / 1f2d57d6a4

Use feature detection and more robust recovering of whitespace for innerHTML in IE8

Andreas Svensson committed Dec 20, 2013 at 20:52 UTC 1f2d57d6a4ad58e36e80dbffb6fd74c27f0b4357
2 files changed +34 -6
src/core/ReactDOMIDOperations.js
+29 -3
@@ -49,7 +49,7 @@ var INVALID_PROPERTY_ERRORS = {
49 */
50 var textContentAccessor = getTextContentAccessor() || 'NA';
51
52 -var LEADING_SPACE = /^ /;
52 +var useWhitespaceWorkaround;
53
54 /**
55 * Operations used to process updates to DOM nodes. This is made injectable via
@@ -124,9 +124,35 @@ var ReactDOMIDOperations = {
124 */
125 updateInnerHTMLByID: function(id, html) {
126 var node = ReactMount.getNode(id);
127 - // HACK: IE8- normalize whitespace in innerHTML, removing leading spaces.
127 +
128 + // IE8: When updating a just created node with innerHTML only leading
129 + // whitespace is removed. When updating an existing node with innerHTML
130 + // whitespace in root TextNodes is also collapsed.
131 // @see quirksmode.org/bugreports/archives/2004/11/innerhtml_and_t.html
129 - node.innerHTML = html.replace(LEADING_SPACE, ' ');
132 +
133 + if (useWhitespaceWorkaround === undefined) {
134 + // Feature detection; only IE8 is known to behave improperly like this.
135 + var temp = document.createElement('div');
136 + temp.innerHTML = ' ';
137 + useWhitespaceWorkaround = temp.innerHTML === '';
138 + }
139 +
140 + if (useWhitespaceWorkaround) {
141 + // Magic theory: IE8 supposedly differentiates between added and updated
142 + // nodes when processing innerHTML, innerHTML on updated nodes suffers
143 + // from worse whitespace behavior. Re-adding a node like this triggers
144 + // the initial and more favorable whitespace behavior.
145 + node.parentNode.replaceChild(node, node);
146 + }
147 +
148 + if (useWhitespaceWorkaround && html.match(/^[ \r\n\t\f]/)) {
149 + // Recover leading whitespace by temporarily prepending any character.
150 + // \uFEFF has the potential advantage of being zero-width/invisible.
151 + node.innerHTML = '\uFEFF' + html;
152 + node.firstChild.deleteData(0, 1);
153 + } else {
154 + node.innerHTML = html;
155 + }
156 },
157
158 /**
src/core/__tests__/ReactDOMIDOperations-test.js
+5 -3
@@ -47,19 +47,21 @@ describe('ReactDOMIDOperations', function() {
47 ).toBe(0);
48 });
49
50 - it('should update innerHTML and special-case whitespace', function() {
50 + it('should update innerHTML and preserve whitespace', function() {
51 var stubNode = document.createElement('div');
52 spyOn(ReactMount, "getNode").andReturn(stubNode);
53
54 + var html = '\n \t <span> \n testContent \t </span> \n \t';
55 +
56 ReactDOMIDOperations.updateInnerHTMLByID(
57 'testID',
56 - ' testContent'
58 + html
59 );
60
61 expect(
62 ReactMount.getNode.argsForCall[0][0]
63 ).toBe('testID');
64
63 - expect(stubNode.innerHTML).toBe('&nbsp;testContent');
65 + expect(stubNode.innerHTML).toBe(html);
66 });
67 });