-
Notifications
You must be signed in to change notification settings - Fork 47.1k
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
Fix iframe reloading when moved #870
Conversation
Updated the PR to use |
Thanks to @pieter-vanderwerff for noticing that |
I investigated further and found that based on the assumptions on |
This would be really great to have! Can you add some tests? |
*/ | ||
function hasImmovableDescendants(node) { | ||
for (var tagName in immovableTagNames) { | ||
if (node.getElementsByTagName(tagName).length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
know if [0]
is any faster than .length
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@spicyj I had tested for 0 in
which apparently is slower (but tested now, and apparently it isn't?)... anyway, it depends on the browser, but [0]
is faster in the slower browsers it seems, so that's the way to go (not that it really matters anyway with 250k call per second on IE8 even). Good call!
http://jsperf.com/queryselectorall-vs-getelementsbytagname/83
@benjamn Definitely, but I'm "new" to testing and I have no idea how I would test this. I could add some "torture test" that makes sure DOM updates never break, that shouldn't be hard. But how would I test "immovable nodes", is it actually feasible to insert an iframe an inject something that I can test for after the test? |
You could have the |
@benjamn I chose the simpler route of just adding a variable to Right now it basically generates a random number of nodes, some with keys, some without. Then permutates them randomly a number of times (including adding and removing) and makes sure that the DOM is exactly what it expects it to be after every render. Then the same with an iframe, and I intend to add at least another one with 2 iframes that may never cross to make sure that works as well. |
@benjamn I've added 4 tests now, all based on the same strategy, mutating a randomly generated set of items based on keys and indices, X number of times and verifying the output at each step. Each test represent a progressively more complex scenario. The last one also tests that the environment behaves as it should (i.e. that immovable objects reload when detached), although it's possible that some browser doesn't have this behavior (test is still useful by itself, but will need to disable the check then). It passes travis-ci which you guys have hooked up to saucelabs right? So seems fine then. |
Thanks to @pieter-vanderwerff for pointing out that If anyone can benchmark this PR as-is and with |
@benjamn I rebased it, but my tests now fails because |
Yeah, |
@spicyj Ah, that makes sense, best solution for this is perhaps just for me to attach it in my tests I guess? As it apparently was an issue before, is there something I should be "aware" of? |
I don't think so, just remove it when you're done. See ce95c3d. |
@spicyj Many thanks, so the issue was way simpler than I thought. :) I've hacked a |
@zpao I will be needing this fix for our service very soon, I have no problem at all proceeding with a custom branch. But I just want to make sure I don't head down a conflicting path/end up with the custom branch indefinitely. Is this fix something you are considering taking (for some future milestone) or are you looking towards an alternative solution/no solution? |
while (childNode.previousSibling !== beforeNode) { | ||
parentNode.insertBefore(childNode.nextSibling, childNode); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want a
invariant(childNode.previousSibling === beforeNode);
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@spicyj Not sure if I follow, seeing as that is the exact condition for the while-loop, it could not possibly be false and trigger the invariant?
EDIT: Nevermind, I see your point, yeah that sounds reasonable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I'm having trouble making sense of what I meant but I'm happy if you understand. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I actually take it back, haha. It temporarily made sense to me in that it would validate that the result of the move was as intended, but if it isn't then it never leaves the loop anyway. So my original reaction stands it seems, if the invariant is reached it would be guaranteed to be true. Unless you had something else in-mind (other than putting it last in the function).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I guess let's just have protection against the case that childNode somehow comes after index
already, which will cause an infinite loop the way you have it written.
(Side note: I found beforeNode
to be a confusing name; I interpreted it as "insert childNode before beforeNode", not "beforeNode should appear before childNode". Not sure what would be clearer though.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that makes sense, invariant(childNode.nextSibling != null)
should be sufficient and simple I think. As for the variable, tricky yes, my current naming was intended to reflect the idea that it's just a different implementation of the same behavior as insertBefore
(insert X before node Y), perhaps index
should rather be renamed to beforeIndex
which could add some sense to beforeNode
(which is just locally caching the value), possibly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think beforeIndex
has the same naming problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it kind of does, insertBeforeIndex
would be the spontaneous solution. But to me that eludes to it being a function and not an index. https://developer.mozilla.org/en-US/docs/Web/API/Node.insertBefore refers to it as referenceElement
, which kind of makes sense and points to my function being badly named, moveChildToBefore
moveChildBefore
moveBefore
... ?
PS. It's also kind of weird that the arguments refers to elements but the descriptions refers to nodes in that URL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe moveChildBefore and referenceElement (or referenceNode).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds fine to me and I think we've been using node everywhere else, but perhaps I've just assumed that. I'll take a look and settle on the naming based on what we use elsewhere.
Fixed docstring + invariant and rebased to fix conflict. |
There are more immovables. The currently focused text field, the current mouse target (avoids an issue with This is generally good but I'm not sure about the strategy around hasImmovableDescendants. I think we can do better. E.g. record keeping inside of React (not including dangerousInnerHTML). |
We can, but it was rather non-trivial as it only applies to ReactDOMComponent (and also adds a potentially depth-linear cost for each tree mutation)... the biggest reason why I chose to go with But I agree it does feel a bit dirty to use |
That's also rather tricky, which is more important if immovables cross paths? I would say: iframe/video/audio/etc > focused text field <> current mouse target Which means we should probably still have a decent fix for the last two. |
Are there any plans to introduce this or has this been scrapped? |
There hasn't been much interest in it and it makes the core more complicated. Do you need it? |
Thank you for your work on this. Technically it’s a problem we don’t have a good solution for, but it significantly complicates the logic for what seems to be a relatively low-priority use case. This is why I’m closing this pull request and adding a new “Resolution: Unsolved” label. We can revisit it later if there is enough interest in seeing this implemented and later supporting that implementation. |
#858
My implementation automatically detect any "immovable" descendant nodes (
iframe
,object
andembed
) insideDOMChildrenOperations
and then perform a strategy where it pre-shifts these immovable DOM nodes into place before proceeding with the regular updates. If there are no immovable DOM nodes then this has virtually zero cost.It uses
getElementsByTagName
to achieve this magic, and contrary to what one might suspect, it is crazy fast. http://jsperf.com/queryselectorall-vs-getelementsbytagname/83 It's so fast that it's certainly faster than propagating and checking attributes on nodes.You can see it in action here:
http://dev.cetrez.com/jsx/2/index2.html (no longer online)
This PR solves the
iframe
issue as far as is physically possible, stopping only short of being able to have iframes swap places (which is impossible). So iframes are actually useful now in React, at (what should be) virtually zero cost!I should perhaps also mention that I would personally find it very useful to have this fixed (be it this PR or any other), as I require
iframe
support for the editor at work. It's fixed in place, but there may be changes in the structure outside of the editor that would cause it to reload. Also, mostcontentEditable
editors use iframes to isolate themselves, so this should find some quite universal usefulness.PS. I tried benchmarking it with
react-bench
, but it's beyond useless as I can get up to ~30% consistent perf diff, while benchmarking with the same React-library.