mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Fix Markup Rendering in IE
This fixes known browser bugs with rendering markup using `innerHTML` in IE ([[http://support.microsoft.com/kb/276228 | here is an example of one]]). This is a subset of what `HTML` (and jQuery) does, and we should eventually consider pulling it out into a separate module to reduce code duplication. For now, this is the minimal set of changes needed to unbreak React in production. We can afford to use a subset of what `HTML` does because we have the luxury of knowing that the markup is generated sanely with proper closing tags, etc.
This commit is contained in:
committed by
Paul O’Shannessy
parent
ed54fff204
commit
83a840656c
@@ -69,7 +69,7 @@ function assertValidProps(props) {
|
||||
* @extends ReactMultiChild
|
||||
*/
|
||||
function ReactNativeComponent(tag, omitClose) {
|
||||
this._tagOpen = '<' + tag + ' ';
|
||||
this._tagOpen = '<' + tag;
|
||||
this._tagClose = omitClose ? '' : '</' + tag + '>';
|
||||
this.tagName = tag.toUpperCase();
|
||||
}
|
||||
|
||||
+82
-12
@@ -16,7 +16,7 @@
|
||||
* @providesModule Danger
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
/*jslint evil: true, sub: true */
|
||||
|
||||
"use strict";
|
||||
|
||||
@@ -50,12 +50,86 @@ if (__DEV__) {
|
||||
};
|
||||
}
|
||||
|
||||
var dummies = {};
|
||||
/**
|
||||
* Dummy container used to render all markup.
|
||||
*/
|
||||
var dummyNode = ExecutionEnvironment.canUseDOM ?
|
||||
document.createElement('div') :
|
||||
null;
|
||||
|
||||
function getParentDummy(parent) {
|
||||
var parentTag = parent.tagName;
|
||||
return dummies[parentTag] ||
|
||||
(dummies[parentTag] = document.createElement(parentTag));
|
||||
/**
|
||||
* Some browsers cannot use `innerHTML` to render certain elements standalone,
|
||||
* so we wrap them, render the wrapped nodes, then extract the desired node.
|
||||
*/
|
||||
var markupWrap = {
|
||||
'option': [1, '<select multiple="true">', '</select>'],
|
||||
'legend': [1, '<fieldset>', '</fieldset>'],
|
||||
'area': [1, '<map>', '</map>'],
|
||||
'param': [1, '<object>', '</object>'],
|
||||
'thead': [1, '<table>', '</table>'],
|
||||
'tr': [2, '<table><tbody>', '</tbody></table>'],
|
||||
'col': [2, '<table><tbody></tbody><colgroup>', '</colgroup></table>'],
|
||||
'td': [3, '<table><tbody><tr>', '</tr></tbody></table>']
|
||||
};
|
||||
markupWrap['optgroup'] = markupWrap['option'];
|
||||
markupWrap['tbody'] = markupWrap['thead'];
|
||||
markupWrap['tfoot'] = markupWrap['thead'];
|
||||
markupWrap['colgroup'] = markupWrap['thead'];
|
||||
markupWrap['caption'] = markupWrap['thead'];
|
||||
markupWrap['th'] = markupWrap['td'];
|
||||
|
||||
/**
|
||||
* In IE8, certain elements cannot render alone, so wrap all elements.
|
||||
*/
|
||||
var defaultWrap = [1, '?<div>', '</div>'];
|
||||
|
||||
/**
|
||||
* Feature detection, remove wraps that are unnecessary for the current browser.
|
||||
*/
|
||||
if (dummyNode) {
|
||||
for (var nodeName in markupWrap) {
|
||||
if (!markupWrap.hasOwnProperty(nodeName)) {
|
||||
continue;
|
||||
}
|
||||
dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';
|
||||
if (dummyNode.firstChild) {
|
||||
markupWrap[nodeName] = null;
|
||||
}
|
||||
}
|
||||
dummyNode.innerHTML = '<link />';
|
||||
if (dummyNode.firstChild) {
|
||||
defaultWrap = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders markup into nodes. The returned HTMLCollection is live and should be
|
||||
* used immediately (or at least before the next invocation to `renderMarkup`).
|
||||
*
|
||||
* NOTE: Extracting the `nodeName` does not require a regular expression match
|
||||
* because we make assumptions about React-generated markup (i.e. there are no
|
||||
* spaces surrounding the opening tag and there is at least one attribute).
|
||||
* @see http://jsperf.com/extract-nodename
|
||||
*
|
||||
* @param {string} markup
|
||||
* @return {*} An HTMLCollection.
|
||||
*/
|
||||
function renderMarkup(markup) {
|
||||
var node = dummyNode;
|
||||
var nodeName = markup.substring(1, markup.indexOf(' '));
|
||||
|
||||
var wrap = markupWrap[nodeName.toLowerCase()] || defaultWrap;
|
||||
if (wrap) {
|
||||
node.innerHTML = wrap[1] + markup + wrap[2];
|
||||
|
||||
var wrapDepth = wrap[0];
|
||||
while (wrapDepth--) {
|
||||
node = node.lastChild;
|
||||
}
|
||||
} else {
|
||||
node.innerHTML = markup;
|
||||
}
|
||||
return node.childNodes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -122,9 +196,7 @@ function dangerouslyInsertMarkupAt(parentNode, markup, index) {
|
||||
if (__DEV__) {
|
||||
validateMarkupParams(parentNode, markup);
|
||||
}
|
||||
var parentDummy = getParentDummy(parentNode);
|
||||
parentDummy.innerHTML = markup;
|
||||
var htmlCollection = parentDummy.childNodes;
|
||||
var htmlCollection = renderMarkup(markup);
|
||||
var afterNode = index ? parentNode.childNodes[index - 1] : null;
|
||||
inefficientlyInsertHTMLCollectionAfter(parentNode, htmlCollection, afterNode);
|
||||
}
|
||||
@@ -143,9 +215,7 @@ function dangerouslyReplaceNodeWithMarkup(childNode, markup) {
|
||||
if (__DEV__) {
|
||||
validateMarkupParams(parentNode, markup);
|
||||
}
|
||||
var parentDummy = getParentDummy(parentNode);
|
||||
parentDummy.innerHTML = markup;
|
||||
var htmlCollection = parentDummy.childNodes;
|
||||
var htmlCollection = renderMarkup(markup);
|
||||
if (__DEV__) {
|
||||
throwIf(htmlCollection.length !== 1, NO_MULTI_MARKUP);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright 2013 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @jsx React.DOM
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
/*jslint evil: true */
|
||||
|
||||
var React = require('React');
|
||||
|
||||
describe('Danger', function() {
|
||||
|
||||
describe('dangerouslyInsertMarkupAt', function() {
|
||||
var Danger;
|
||||
var transaction;
|
||||
|
||||
beforeEach(function() {
|
||||
require('mock-modules').dumpCache();
|
||||
Danger = require('Danger');
|
||||
|
||||
var ReactReconcileTransaction = require('ReactReconcileTransaction');
|
||||
transaction = new ReactReconcileTransaction();
|
||||
});
|
||||
|
||||
it('should render markup', function() {
|
||||
var markup = (<div />).mountComponent('.rX', transaction);
|
||||
var parent = document.createElement('div');
|
||||
|
||||
Danger.dangerouslyInsertMarkupAt(parent, markup, 0);
|
||||
|
||||
expect(parent.innerHTML).toBe('<div data-reactid=".rX"></div>');
|
||||
});
|
||||
|
||||
it('should render markup with props', function() {
|
||||
var markup = (<div className="foo" />).mountComponent('.rX', transaction);
|
||||
var parent = document.createElement('div');
|
||||
|
||||
Danger.dangerouslyInsertMarkupAt(parent, markup, 0);
|
||||
|
||||
expect(parent.innerHTML).toBe(
|
||||
'<div class="foo" data-reactid=".rX"></div>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render wrapped markup', function() {
|
||||
var markup = (<th />).mountComponent('.rX', transaction);
|
||||
var parent = document.createElement('div');
|
||||
|
||||
Danger.dangerouslyInsertMarkupAt(parent, markup, 0);
|
||||
|
||||
expect(parent.innerHTML).toBe('<th data-reactid=".rX"></th>');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -54,7 +54,7 @@ describe('ReactServerRendering', function() {
|
||||
}
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">hello world</span>'
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">hello world</span>'
|
||||
);
|
||||
});
|
||||
|
||||
@@ -77,8 +77,8 @@ describe('ReactServerRendering', function() {
|
||||
}
|
||||
);
|
||||
expect(response).toMatch(
|
||||
'<div ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<div ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">My name is </span>' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">child</span>' +
|
||||
'</span>' +
|
||||
@@ -130,7 +130,7 @@ describe('ReactServerRendering', function() {
|
||||
);
|
||||
|
||||
expect(response).toMatch(
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">Component name: </span>' +
|
||||
'<span ' + ReactID.ATTR_NAME + '="[^"]+">TestComponent</span>' +
|
||||
'</span>'
|
||||
|
||||
Reference in New Issue
Block a user