-
Notifications
You must be signed in to change notification settings - Fork 27.4k
Interpolations containing HTML entities fail in IE11 when MutationObservers are active #11781
Comments
Using |
This looks like a real issue, unlucky I do not have access to an IE11 so to diagnose this I will need some help |
Took a brief look and here is what is going on:
So, in the former case, (I still don't know why this happens and if Angular can do something about it.) @lgalfaso, I do have access to IE11; let me know if I can be of any help. |
@gkalpak is this also true if, after creating the |
@gkalpak or even simpler (even when not better), calling |
The first part is clear, this is an issue with IE11 as text elements must not be split. Now, if calling This implies that any solution will not end up being pretty. All that said, I think that there is nothing for angular to do to workaround the IE11 bug, but I will leave this issue open for people to comment and if someone wants to create a IE11 specific workaround, then to post it here. |
IE11 + MutationObserver does some weird stuff after encountering the interpolation symbol ( One possible work-around is to use non-special characters as interpolation symbols. I am not sure what qualifies as "non-special", but alphabetic characters do. So, for example, using |
IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
@lgalfaso: I commented on the PR |
IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
The fixed test is supposed to test a fix for an IE11 bug/peculiarity that arises when using a specifically configured MutationObserver on the page (see angular#11781 for more info). The configuration contained a typo (`sublist` instead of `subtree`), which effectively failed to set up the MutationObserver in a way that would make the IE11 bug appear.
The fixed test is supposed to test a fix for an IE11 bug/peculiarity that arises when using a specifically configured MutationObserver on the page (see angular#11781 for more info). The configuration contained a typo (`sublist` instead of `subtree`), which effectively failed to set up the MutationObserver in a way that would make the IE11 bug appear. Closes angular#12061
The fixed test is supposed to test a fix for an IE11 bug/peculiarity that arises when using a specifically configured MutationObserver on the page (see #11781 for more info). The configuration contained a typo (`sublist` instead of `subtree`), which effectively failed to set up the MutationObserver in a way that would make the IE11 bug appear. Closes #12061
Spent a whole day nailing down this issue in one of our apps just to find out it is already reported and resolved :) |
😆 |
Got to keep on the bleeding edge @shahata ! |
IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
The fixed test is supposed to test a fix for an IE11 bug/peculiarity that arises when using a specifically configured MutationObserver on the page (see angular#11781 for more info). The configuration contained a typo (`sublist` instead of `subtree`), which effectively failed to set up the MutationObserver in a way that would make the IE11 bug appear. Closes angular#12061
Backport angular#11796 to 1.2 branch. IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
Backport angular#11796 to 1.2 branch. IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
Backport angular#11796 to 1.2 branch. IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Closes angular#11781
Backport angular#11796 to 1.2 branch. IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Also had to modify npm-shrinkwrap.json because i@0.3.2 was unpublished from npm. Closes angular#11781
Backport #11796 to 1.2 branch. IE11 MutationObserver breaks consecutive text nodes into several text nodes. This patch merges consecutive text nodes into a single node before looking for interpolations. Also had to modify npm-shrinkwrap.json because i@0.3.2 was unpublished from npm. Closes #11781 Closes #12613
A similar error to this is coming up again in IE11 but only in CJK and Cyrillic languages. Must be related to how IE11 parses non-latin languages? |
It is splitting the interpolated string after the curly brackets. So it is becoming {{ |
@jshoudy11, do you have a demo ? |
This is actually a public facing issue on our website right now.
|
You should open a new issue for that so it's easier to handle. We'll mark it as origin: google too. And a small self contained demo is still valuable if you have one. |
I couldn't reproduce it. It also doesn't make sense that changing an app setting would affect how the browser parses stuff. It was either a different problem or is related to the user's locale. If you can reliably reproduce the problem, please open a new issue as @Narretz suggested (providing all relevant info, such as OS/version, browser/version, your locale etc). |
@jshoudy11 are you using |
No. Not using ngMessageFormat. I'm trying to get a self contained demo that repros. I'll open a new issue if I get that. |
Also to get the repro follow the same instructions as before except look for "{{::link.text}} in the bottom right of the reloaded page |
As explained in angular#11781 and angular#14924, IE11 can (under certain circumstances) break up a text node into multiple consecutive ones, breaking interpolated expressions (e.g. `{{'foo'}}` would become `{{` + `'foo'}}`). To work-around this IE11 bug, angular#11796 introduced extra logic to merge consecutive text nodes (on IE11 only), which relies on the text nodes' having the same `parentNode`. This approach works fine in the common case, where `compileNodes` is called with a live NodeList object, because removing a text node from its parent will automatically update the latter's `.childNodes` NodeList. It falls short though, when calling `compileNodes` with either a jqLite/jQuery collection or an Array. In fails in two ways: 1. If the text nodes do not have a parent at the moment of compiling, there will be no merging. (This happens for example on directives with `$transclude: {...}`.) 2. If the text nodes do have a parent, just removing a text node from its parent does **not** remove it from the collection/array, which means that the merged text nodes will still get compiled and linked (and possibly be displayed in the view). E.g. `['{{text1}}', '{{text2}}', '{{text3}}']` will become `['{{text1}}{{text2}}{{text3}}', '{{text2}}', '{{text3}}']`. -- This commit works around the above problems by: 1. Merging consecutive text nodes in the provided list, even if they have no parent. 2. When merging a txt node, explicitly remove it from the list (unless it is a live, auto-updating list). This can nonetheless have undesirable (albeit rare) side effects by overzealously merging text nodes that are not meant to be merged (see "BREAKING CHANGE" section below). Fixes angular#14924 BREAKING CHANGE: **Note:** Everything described below affects **IE11 only**. Previously, consecutive text nodes would not get merged if they had no parent. They will now, which might have unexpectd side effects in the following cases: 1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly: ```js // Assuming: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = $compile(textNodes)($rootScope); // Before: console.log(compiledNodes.length); // 3 console.log(compiledNodes.text()); // {{'foo'}} // After: console.log(compiledNodes.length); // 1 console.log(compiledNodes.text()); // foo // To get the old behavior, compile each node separately: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = angular.element(textNodes.map(function (node) { return $compile(node)($rootScope)[0]; })); ``` 2. Using multi-slot transclusion with non-consecutive, default-content text nodes (that form interpolated expressions when merged): ```js // Assuming the following component: .compoent('someThing', { template: '<ng-transclude><!-- Default content goes here --></ng-transclude>' transclude: { ignored: 'veryImportantContent' } }) ``` ```html <!-- And assuming the following view: <some-thing> {{ <very-important-content>Nooot</very-important-content> 'foo'}} </some-thing> <!-- Before: --> <some-thing> <ng-transclude> {{ <-- Two separate 'foo'}} <-- text nodes </ng-transclude> </some-thing> <!-- After: --> <some-thing> <ng-transclude> foo <-- The text nodes were merged into `{{'foo'}}`, which was then interpolated </ng-transclude> </some-thing> <!-- To (visually) get the old behavior, wrap top-level text-nodes on <!-- multi-slot transclusion directives into `<span>`; e.g.: <some-thing> <span>{{</span> <very-important-content>Nooot</very-important-content> <span>'foo'}}</span> </some-thing> <!-- Result: --> <some-thing> <ng-transclude> <span>{{</span> <-- Two separate <span>'foo'}}</span> <-- nodes </ng-transclude> </some-thing> ```
As explained in angular#11781 and angular#14924, IE11 can (under certain circumstances) break up a text node into multiple consecutive ones, breaking interpolated expressions (e.g. `{{'foo'}}` would become `{{` + `'foo'}}`). To work-around this IE11 bug, angular#11796 introduced extra logic to merge consecutive text nodes (on IE11 only), which relies on the text nodes' having the same `parentNode`. This approach works fine in the common case, where `compileNodes` is called with a live NodeList object, because removing a text node from its parent will automatically update the latter's `.childNodes` NodeList. It falls short though, when calling `compileNodes` with either a jqLite/jQuery collection or an Array. In fails in two ways: 1. If the text nodes do not have a parent at the moment of compiling, there will be no merging. (This happens for example on directives with `$transclude: {...}`.) 2. If the text nodes do have a parent, just removing a text node from its parent does **not** remove it from the collection/array, which means that the merged text nodes will still get compiled and linked (and possibly be displayed in the view). E.g. `['{{text1}}', '{{text2}}', '{{text3}}']` will become `['{{text1}}{{text2}}{{text3}}', '{{text2}}', '{{text3}}']`. -- This commit works around the above problems by: 1. Merging consecutive text nodes in the provided list, even if they have no parent. 2. When merging a txt node, explicitly remove it from the list (unless it is a live, auto-updating list). This can nonetheless have undesirable (albeit rare) side effects by overzealously merging text nodes that are not meant to be merged (see "BREAKING CHANGE" section below). Fixes angular#14924 BREAKING CHANGE: **Note:** Everything described below affects **IE11 only**. Previously, consecutive text nodes would not get merged if they had no parent. They will now, which might have unexpectd side effects in the following cases: 1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly: ```js // Assuming: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = $compile(textNodes)($rootScope); // Before: console.log(compiledNodes.length); // 3 console.log(compiledNodes.text()); // {{'foo'}} // After: console.log(compiledNodes.length); // 1 console.log(compiledNodes.text()); // foo // To get the old behavior, compile each node separately: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = angular.element(textNodes.map(function (node) { return $compile(node)($rootScope)[0]; })); ``` 2. Using multi-slot transclusion with non-consecutive, default-content text nodes (that form interpolated expressions when merged): ```js // Assuming the following component: .compoent('someThing', { template: '<ng-transclude><!-- Default content goes here --></ng-transclude>' transclude: { ignored: 'veryImportantContent' } }) ``` ```html <!-- And assuming the following view: --> <some-thing> {{ <very-important-content>Nooot</very-important-content> 'foo'}} </some-thing> <!-- Before: --> <some-thing> <ng-transclude> {{ <-- Two separate 'foo'}} <-- text nodes </ng-transclude> </some-thing> <!-- After: --> <some-thing> <ng-transclude> foo <-- The text nodes were merged into `{{'foo'}}`, which was then interpolated </ng-transclude> </some-thing> <!-- To (visually) get the old behavior, wrap top-level text-nodes on --> <!-- multi-slot transclusion directives into `<span>`; e.g.: --> <some-thing> <span>{{</span> <very-important-content>Nooot</very-important-content> <span>'foo'}}</span> </some-thing> <!-- Result: --> <some-thing> <ng-transclude> <span>{{</span> <-- Two separate <span>'foo'}}</span> <-- nodes </ng-transclude> </some-thing> ```
As explained in #11781 and #14924, IE11 can (under certain circumstances) break up a text node into multiple consecutive ones, breaking interpolated expressions (e.g. `{{'foo'}}` would become `{{` + `'foo'}}`). To work-around this IE11 bug, #11796 introduced extra logic to merge consecutive text nodes (on IE11 only), which relies on the text nodes' having the same `parentNode`. This approach works fine in the common case, where `compileNodes` is called with a live NodeList object, because removing a text node from its parent will automatically update the latter's `.childNodes` NodeList. It falls short though, when calling `compileNodes` with either a jqLite/jQuery collection or an Array. In fails in two ways: 1. If the text nodes do not have a parent at the moment of compiling, there will be no merging. (This happens for example on directives with `transclude: {...}`.) 2. If the text nodes do have a parent, just removing a text node from its parent does **not** remove it from the collection/array, which means that the merged text nodes will still get compiled and linked (and possibly be displayed in the view). E.g. `['{{text1}}', '{{text2}}', '{{text3}}']` will become `['{{text1}}{{text2}}{{text3}}', '{{text2}}', '{{text3}}']`. -- This commit works around the above problems by: 1. Merging consecutive text nodes in the provided list, even if they have no parent. 2. When merging a text node, explicitly remove it from the list (unless it is a live, auto-updating list). This can nonetheless have undesirable (albeit rare) side effects by overzealously merging text nodes that are not meant to be merged (see the "BREAKING CHANGE" section below). Fixes #14924 Closes #15025 BREAKING CHANGE: **Note:** Everything described below affects **IE11 only**. Previously, consecutive text nodes would not get merged if they had no parent. They will now, which might have unexpectd side effects in the following cases: 1. Passing an array or jqLite/jQuery collection of parent-less text nodes to `$compile` directly: ```js // Assuming: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = $compile(textNodes)($rootScope); // Before: console.log(compiledNodes.length); // 3 console.log(compiledNodes.text()); // {{'foo'}} // After: console.log(compiledNodes.length); // 1 console.log(compiledNodes.text()); // foo // To get the old behavior, compile each node separately: var textNodes = [ document.createTextNode('{{'), document.createTextNode('"foo"'), document.createTextNode('}}') ]; var compiledNodes = angular.element(textNodes.map(function (node) { return $compile(node)($rootScope)[0]; })); ``` 2. Using multi-slot transclusion with non-consecutive, default-content text nodes (that form interpolated expressions when merged): ```js // Assuming the following component: .compoent('someThing', { template: '<ng-transclude><!-- Default content goes here --></ng-transclude>' transclude: { ignored: 'veryImportantContent' } }) ``` ```html <!-- And assuming the following view: --> <some-thing> {{ <very-important-content>Nooot</very-important-content> 'foo'}} </some-thing> <!-- Before: --> <some-thing> <ng-transclude> {{ <-- Two separate 'foo'}} <-- text nodes </ng-transclude> </some-thing> <!-- After: --> <some-thing> <ng-transclude> foo <-- The text nodes were merged into `{{'foo'}}`, which was then interpolated </ng-transclude> </some-thing> <!-- To (visually) get the old behavior, wrap top-level text nodes on --> <!-- multi-slot transclusion directives into `<span>` elements; e.g.: --> <some-thing> <span>{{</span> <very-important-content>Nooot</very-important-content> <span>'foo'}}</span> </some-thing> <!-- Result: --> <some-thing> <ng-transclude> <span>{{</span> <-- Two separate <span>'foo'}}</span> <-- nodes </ng-transclude> </some-thing> ```
Text nodes that contain unicode / HTML entities are not properly interpolated in IE11 when there is a
MutationObserver
active. Consider the following example.The output is:
This works. — {{ "This doesn't." }}
If you remove the
MutationObserver
the code works as expected. If you don't setsubtree: true
the code works as expected. If you remove the entity it also works as expected. If instead you replace the entity with the actual character (—) it remains broken. It's also broken in 1.3.We recently started seeing this bug because Wordpress has some Emoji polyfills that use
MutationObserver
. This issue breaks interpolation even when the Angular application is running in an iframe and the code that is attaching theMutationObserver
is in the parent frame, which seems pretty crazy. Because of this we seek a workaround on the Angular side, as we can't change every site that might embed Angular in an iframe -- perhaps this is a bug in IE.The text was updated successfully, but these errors were encountered: