From 1f427830ba6d683795a191079c7eabcdb27c7db6 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 7 Jun 2016 14:49:50 +1000 Subject: [PATCH 1/6] Workaround IE lacking innerHTML on SVG elements --- src/renderers/dom/client/utils/DOMLazyTree.js | 5 +++-- src/renderers/dom/client/utils/setInnerHTML.js | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/renderers/dom/client/utils/DOMLazyTree.js b/src/renderers/dom/client/utils/DOMLazyTree.js index 269f5c37145a2..c6c0b98f9e8ec 100644 --- a/src/renderers/dom/client/utils/DOMLazyTree.js +++ b/src/renderers/dom/client/utils/DOMLazyTree.js @@ -12,6 +12,7 @@ 'use strict'; var DOMNamespaces = require('DOMNamespaces'); +var setInnerHTML = require('setInnerHTML'); var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); var setTextContent = require('setTextContent'); @@ -50,7 +51,7 @@ function insertTreeChildren(tree) { insertTreeBefore(node, children[i], null); } } else if (tree.html != null) { - node.innerHTML = tree.html; + setInnerHTML(node, tree.html); } else if (tree.text != null) { setTextContent(node, tree.text); } @@ -96,7 +97,7 @@ function queueHTML(tree, html) { if (enableLazy) { tree.html = html; } else { - tree.node.innerHTML = html; + setInnerHTML(tree.node, html); } } diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index 1e491bfaf3820..0cc53f1023078 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -18,6 +18,9 @@ var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/; var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); +// SVG temp container for IE lacking innerHTML +var reusableSVGContainer; + /** * Set the innerHTML property of a node, ensuring that whitespace is preserved * even in IE8. @@ -28,7 +31,19 @@ var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunc */ var setInnerHTML = createMicrosoftUnsafeLocalFunction( function(node, html) { - node.innerHTML = html; + // IE does not have innerHTML for SVG nodes, so instead we inject the + // new markup in a temp node and then move the child nodes across into + // the target node + if (typeof SVGElement !== 'undefined' && node instanceof SVGElement) { + reusableSVGContainer = reusableSVGContainer || document.createElement('div'); + reusableSVGContainer.innerHTML = '' + html + ''; + var svg = reusableSVGContainer.firstChild; + while (svg.firstChild) { + node.appendChild(svg.firstChild); + } + } else { + node.innerHTML = html; + } } ); From 35893571ade71860d5c402fc15197652a1a50c01 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 7 Jun 2016 21:10:46 +1000 Subject: [PATCH 2/6] Add tests for setInnerHTML --- .../client/utils/__tests__/setInnerHTML.js | 36 +++++++++++++++++++ .../dom/client/utils/setInnerHTML.js | 6 ++-- 2 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 src/renderers/dom/client/utils/__tests__/setInnerHTML.js diff --git a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js new file mode 100644 index 0000000000000..886f0c216f027 --- /dev/null +++ b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js @@ -0,0 +1,36 @@ +/** + * Copyright 2016-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var setInnerHTML = require('setInnerHTML'); + +describe('setInnerHTML', function() { + describe('when the node has innerHTML property', () => { + it('sets innerHTML on it', function() { + var node = document.createElement('div'); + var html = '

hello

'; + setInnerHTML(node, html); + expect(node.innerHTML).toBe(html); + }); + }); + + // SVGElements on IE don't have innerHTML + describe('when the node does not innerHTML property', () => { + it('sets innerHTML on it', function() { + var node = document.createElement('svg'); + delete node.innerHTML; + var html = ''; + setInnerHTML(node, html); + expect(node.innerHTML).toBe(html); + }); + }); +}); diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index 0cc53f1023078..0891580fff3bd 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -34,15 +34,15 @@ var setInnerHTML = createMicrosoftUnsafeLocalFunction( // IE does not have innerHTML for SVG nodes, so instead we inject the // new markup in a temp node and then move the child nodes across into // the target node - if (typeof SVGElement !== 'undefined' && node instanceof SVGElement) { + if (node.innerHTML) { + node.innerHTML = html; + } else { reusableSVGContainer = reusableSVGContainer || document.createElement('div'); reusableSVGContainer.innerHTML = '' + html + ''; var svg = reusableSVGContainer.firstChild; while (svg.firstChild) { node.appendChild(svg.firstChild); } - } else { - node.innerHTML = html; } } ); From 382251b1c96d82bf66e82bd249cf73b631fed959 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Tue, 7 Jun 2016 22:06:28 +1000 Subject: [PATCH 3/6] Correctly check if node has innerHTML property --- src/renderers/dom/client/utils/setInnerHTML.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index 0891580fff3bd..f606d75556096 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -31,11 +31,12 @@ var reusableSVGContainer; */ var setInnerHTML = createMicrosoftUnsafeLocalFunction( function(node, html) { + if ('innerHTML' in node) { + node.innerHTML = html; + // IE does not have innerHTML for SVG nodes, so instead we inject the // new markup in a temp node and then move the child nodes across into // the target node - if (node.innerHTML) { - node.innerHTML = html; } else { reusableSVGContainer = reusableSVGContainer || document.createElement('div'); reusableSVGContainer.innerHTML = '' + html + ''; From 0472e33eaf0cb06e2edd1587db1db6231019588a Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 8 Jun 2016 11:02:17 +1000 Subject: [PATCH 4/6] Ensure tests for setInnerHTML actually tests both codepaths --- src/renderers/dom/client/utils/__tests__/setInnerHTML.js | 6 ++++-- src/renderers/dom/client/utils/setInnerHTML.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js index 886f0c216f027..0b8a861cb8ed2 100644 --- a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js +++ b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js @@ -27,10 +27,12 @@ describe('setInnerHTML', function() { describe('when the node does not innerHTML property', () => { it('sets innerHTML on it', function() { var node = document.createElement('svg'); - delete node.innerHTML; + Object.defineProperty(node, 'innerHTML', { get: function() {} }); + var html = ''; setInnerHTML(node, html); - expect(node.innerHTML).toBe(html); + + expect(node.outerHTML).toBe('' + html + ''); }); }); }); diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index f606d75556096..4dd981bce8a83 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -31,7 +31,7 @@ var reusableSVGContainer; */ var setInnerHTML = createMicrosoftUnsafeLocalFunction( function(node, html) { - if ('innerHTML' in node) { + if (typeof node.innerHTML !== 'undefined') { node.innerHTML = html; // IE does not have innerHTML for SVG nodes, so instead we inject the From da366c61512403820983b462e203ecfc5b0ac565 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 8 Jun 2016 22:51:17 +1000 Subject: [PATCH 5/6] Provide mock element for setInnerHTML tests --- src/renderers/dom/client/utils/__tests__/setInnerHTML.js | 9 +++++---- src/renderers/dom/client/utils/setInnerHTML.js | 8 ++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js index 0b8a861cb8ed2..056da7e3a2704 100644 --- a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js +++ b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js @@ -26,13 +26,14 @@ describe('setInnerHTML', function() { // SVGElements on IE don't have innerHTML describe('when the node does not innerHTML property', () => { it('sets innerHTML on it', function() { - var node = document.createElement('svg'); - Object.defineProperty(node, 'innerHTML', { get: function() {} }); + // Create a mock node that lacks innerHTML + var node = { appendChild: jasmine.createSpy() }; - var html = ''; + var html = ''; setInnerHTML(node, html); - expect(node.outerHTML).toBe('' + html + ''); + expect(node.appendChild.calls.argsFor(0)[0].outerHTML).toBe(''); + expect(node.appendChild.calls.argsFor(1)[0].outerHTML).toBe(''); }); }); }); diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index 4dd981bce8a83..2af05560e253b 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -31,7 +31,7 @@ var reusableSVGContainer; */ var setInnerHTML = createMicrosoftUnsafeLocalFunction( function(node, html) { - if (typeof node.innerHTML !== 'undefined') { + if ('innerHTML' in node) { node.innerHTML = html; // IE does not have innerHTML for SVG nodes, so instead we inject the @@ -40,9 +40,9 @@ var setInnerHTML = createMicrosoftUnsafeLocalFunction( } else { reusableSVGContainer = reusableSVGContainer || document.createElement('div'); reusableSVGContainer.innerHTML = '' + html + ''; - var svg = reusableSVGContainer.firstChild; - while (svg.firstChild) { - node.appendChild(svg.firstChild); + var newNodes = reusableSVGContainer.firstChild.childNodes; + for (var i = 0; i < newNodes.length; i++) { + node.appendChild(newNodes[i]); } } } From 446759bd5374fb248fae47e593a82af708fdcf1c Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 8 Jun 2016 23:03:49 +1000 Subject: [PATCH 6/6] Only use SVG setInnerHTML workaround for SVG elements --- .../dom/client/utils/__tests__/setInnerHTML.js | 11 +++++++---- src/renderers/dom/client/utils/setInnerHTML.js | 8 ++++---- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js index 056da7e3a2704..2f4eccec4e8eb 100644 --- a/src/renderers/dom/client/utils/__tests__/setInnerHTML.js +++ b/src/renderers/dom/client/utils/__tests__/setInnerHTML.js @@ -12,6 +12,7 @@ 'use strict'; var setInnerHTML = require('setInnerHTML'); +var DOMNamespaces = require('DOMNamespaces'); describe('setInnerHTML', function() { describe('when the node has innerHTML property', () => { @@ -23,11 +24,13 @@ describe('setInnerHTML', function() { }); }); - // SVGElements on IE don't have innerHTML - describe('when the node does not innerHTML property', () => { + describe('when the node does not have an innerHTML property', () => { it('sets innerHTML on it', function() { - // Create a mock node that lacks innerHTML - var node = { appendChild: jasmine.createSpy() }; + // Create a mock node that looks like an SVG in IE (without innerHTML) + var node = { + namespaceURI: DOMNamespaces.svg, + appendChild: jasmine.createSpy(), + }; var html = ''; setInnerHTML(node, html); diff --git a/src/renderers/dom/client/utils/setInnerHTML.js b/src/renderers/dom/client/utils/setInnerHTML.js index 2af05560e253b..59348fe3c3f70 100644 --- a/src/renderers/dom/client/utils/setInnerHTML.js +++ b/src/renderers/dom/client/utils/setInnerHTML.js @@ -12,6 +12,7 @@ 'use strict'; var ExecutionEnvironment = require('ExecutionEnvironment'); +var DOMNamespaces = require('DOMNamespaces'); var WHITESPACE_TEST = /^[ \r\n\t\f]/; var NONVISIBLE_TEST = /<(!--|link|noscript|meta|script|style)[ \r\n\t\f\/>]/; @@ -31,19 +32,18 @@ var reusableSVGContainer; */ var setInnerHTML = createMicrosoftUnsafeLocalFunction( function(node, html) { - if ('innerHTML' in node) { - node.innerHTML = html; - // IE does not have innerHTML for SVG nodes, so instead we inject the // new markup in a temp node and then move the child nodes across into // the target node - } else { + if (node.namespaceURI === DOMNamespaces.svg && !('innerHTML' in node)) { reusableSVGContainer = reusableSVGContainer || document.createElement('div'); reusableSVGContainer.innerHTML = '' + html + ''; var newNodes = reusableSVGContainer.firstChild.childNodes; for (var i = 0; i < newNodes.length; i++) { node.appendChild(newNodes[i]); } + } else { + node.innerHTML = html; } } );