-
Notifications
You must be signed in to change notification settings - Fork 49.8k
[Fiber] Support SVG #8490
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
[Fiber] Support SVG #8490
Conversation
Otherwise, the parent gets skipped next time. We could probably create it later but this seems simpler.
I don't understand why they changed but probably related to us moving some work into begin phase?
Previously I was pushing nodes on the parent stack regardless of whether they were already in current or not. As a result, insertions during updates were duplicated, and nodes were added to existing parents before commit phase. Luckily we have a test that caught that.
I'm not 100% sure this is right but I can't get tests to fail.
I was confused by th HACK comment so I learned how DOM and SVG work with casing and tried to write a more descriptive comment. It also seems like passing fiber.type into finalizeInitialChildren() is a potential problem because DOM code assumes tag is lowercase. So I added a similar "hack" to finalizeInitialChildren() that is identical to the one we have prepareUpdate() so if we fix them later, we fix both.
We can address this later separately as it is a more hot path. This doesn't affect correctness of SVG container behavior.
This tests the "jump" reuse code path in particular.
This way createInstance() depends on the innermost container only for reading the namespace.
While we might want to create instance in the begin phase, we shouldn't let DOM guide reconciler design. Instead, we are adding a new concept of "host context". In case of ReactDOMFiber, it's just the current namespace. We are keeping a stack of host context values, ignoring those that are referentially equal. The renderer receives the parent context and type, and can return a new context.
It wasn't quite clear from the API which context was being returned by the renderer. Changed the API to specifically ask for child context, and thus to pop it before getting the current context. This fixes the case with <foreignObject> to which I intended to give SVG namespace.
We create stacks lazily so that if portal doesn't contain <svg>s, we don't need to allocate. We also reuse the same object for portal host context state instead of creating a new one every time.
| </ErrorBoundary> | ||
| { | ||
| /* | ||
| * TODO: enable. Currently this leads to stack overflow |
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.
Note: this fails on master too so it's not a regression.
|
Meh. This breaks other tests. :-( |
| function pushHostContext(fiber : Fiber) : void { | ||
| const parentHostContext = getHostContext(); | ||
| const currentHostContext = getChildHostContext(parentHostContext, fiber.type); | ||
| if (parentHostContext === currentHostContext) { |
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.
Here's the goal: This whole thing needs to be inlinable so that this check doesn't cause extra cost for non-context changing components. Currently this function is called multiple times which makes this a tradeoff a compiler might not do. Also getHostContext is similar. And regardless this needs at least two checks, one to compare the type string and one to compare the object pointers.
| } | ||
|
|
||
| // If we meet any portals, we'll go deeper. | ||
| let containerStack : Array<ContainerState> = []; |
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.
If you have a separate variable for the top value, it becomes faster to read since we do a lot more reads than we do push/pop. Same for the context.
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.
Okay. I was doing that before but wasn't sure it's important. Is it because property access can be slow?
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.
Since it's a numeric property it's not as bad as property access but at least it's two more indirections to figure out where the array object is and then where the content allocation is. Assuming the optimization is good. More with less optimization which is our common case.
|
What's the failing tests? What's remaining here before we can land and iterate? |
|
Shouldn't be much. I'll look today! |
|
Okay tests are passing now. |
| getChildHostContext() { | ||
| return null; | ||
| }, | ||
|
|
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.
We'll need to replicate this to the separate repo. cc @bvaughn
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.
Roger that. I'll append it to my existing sync PR.
Edit: Added to reactjs/react-art/pull/109 and merged.
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.
nit: I think it would be nice for us to alpha-sort HostConfig props to make it easier to scan for differences between fiber renderers. (At least it helps when things are changing fairly often due to fiber being in-flux.)
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.
We should make these optional so that they don't have to be added to every renderer. That way we can also do things like:
const { getChildHostContext } = config;
if (getChildHostContext) {
var data = getSomeData();
return getChildHostContext(data);
}That way if the compiler knows that it is always undefined, it can dead-code eliminate everything in the if statement. That's part of the reason I'd like to compile the renderer and host config together into one unit instead of making them share code.
|
I can see if I can restructure it for the inlining. I can just send a PR for my comments instead. |
|
I found a few annoying portal bugs but haven’t been able to fix them yet. |
* Test that SVG elements get created with the right namespace * Pass root to the renderer methods * Keep track of host instances and containers * Keep instances instead of fibers on the stack * Create text instances in begin phase * Create instance before bailing on offscreen children Otherwise, the parent gets skipped next time. We could probably create it later but this seems simpler. * Tweak magic numbers in incremental tests I don't understand why they changed but probably related to us moving some work into begin phase? * Only push newly created nodes on the parent stack Previously I was pushing nodes on the parent stack regardless of whether they were already in current or not. As a result, insertions during updates were duplicated, and nodes were added to existing parents before commit phase. Luckily we have a test that caught that. * Fix lint * Fix Flow I had to wrap HostContext API into a closure so that it's parameterizeable with I and C. * Use the same destructuring style in scheduler as everywhere else * Remove branches that don't seem to run anymore I'm not 100% sure this is right but I can't get tests to fail. * Be explicit about the difference between type and tag I was confused by th HACK comment so I learned how DOM and SVG work with casing and tried to write a more descriptive comment. It also seems like passing fiber.type into finalizeInitialChildren() is a potential problem because DOM code assumes tag is lowercase. So I added a similar "hack" to finalizeInitialChildren() that is identical to the one we have prepareUpdate() so if we fix them later, we fix both. * Save and restore host context when pushing and popping portals * Revert parent context and adding children in the begin phase We can address this later separately as it is a more hot path. This doesn't affect correctness of SVG container behavior. * Add a test for SVG updates This tests the "jump" reuse code path in particular. * Record tests * Read ownerDocument from the root container instance This way createInstance() depends on the innermost container only for reading the namespace. * Track namespaces instead of creating instances early While we might want to create instance in the begin phase, we shouldn't let DOM guide reconciler design. Instead, we are adding a new concept of "host context". In case of ReactDOMFiber, it's just the current namespace. We are keeping a stack of host context values, ignoring those that are referentially equal. The renderer receives the parent context and type, and can return a new context. * Pop child context before reading own context and clarify API It wasn't quite clear from the API which context was being returned by the renderer. Changed the API to specifically ask for child context, and thus to pop it before getting the current context. This fixes the case with <foreignObject> to which I intended to give SVG namespace. * Give SVG namespace to <svg> itself * Don't allocate unnecessarily when reconciling portals We create stacks lazily so that if portal doesn't contain <svg>s, we don't need to allocate. We also reuse the same object for portal host context state instead of creating a new one every time. * Add more tests for edge cases * Fix up math namespace * Maintain a separate container stack * Fix rebase mistakes * Unwind context on errors * Reset the container state when reusing the object * Add getChildHostContext() to ReactART * Record tests
Based on #8417 + discussion in #8475 (comment).