Skip to content
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 by adding a container stack #8417

Closed
wants to merge 22 commits into from

Conversation

gaearon
Copy link
Collaborator

@gaearon gaearon commented Nov 24, 2016

I added a test for SVG. For it to pass, we need to keep track of the closest host container (e.g. svg or foreignobject) on the stack. Since we need the stack anyway, I also used it to append initial children in the begin phase instead of the complete phase. I am using two separate stacks: one for "container" nodes (such as svg and foreignobject) and another one for any "new" nodes so that we can add children to them immediately. Changed to use a single container stack per review comments. I can do the parent stack in a follow up.

@gaearon gaearon force-pushed the fiber-svg branch 2 times, most recently from cb3e491 to a755f74 Compare November 25, 2016 17:42
@gaearon gaearon changed the title [Fiber][WIP] Support SVG and append children in the begin phase [Fiber] Append children in the begin phase and support SVG Nov 25, 2016
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 25, 2016

@sebmarkbage This is sort of ready for review. Portals might be broken but I have a todo for that.

@gaearon
Copy link
Collaborator Author

gaearon commented Nov 25, 2016

Should work with portals too now.

@gaearon
Copy link
Collaborator Author

gaearon commented Nov 25, 2016

@gaearon
Copy link
Collaborator Author

gaearon commented Nov 25, 2016

Note that I haven't tried unifying this with portals yet. I'm not sure if it's helpful or not.

@sebmarkbage
Copy link
Collaborator

Any way we can split the container piece from the parent piece (that creates in beginWork)? The second one is a hot path and needs further evaluation.

@gaearon gaearon changed the title [Fiber] Append children in the begin phase and support SVG [Fiber] Support SVG by adding a container stack Nov 26, 2016
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 26, 2016

Removed the parent stack and moved initial insertions back into complete phase.

We still need to create the instances in the begin phase so that we have access to root container instance in ReactDOMFiberComponent.createElement() and can determine the namespace and owner document.

@@ -453,8 +497,15 @@ module.exports = function<T, P, I, TI, C>(
case HostComponent:
return updateHostComponent(current, workInProgress);
case HostText:
// Nothing to do here. This is terminal. We'll do the completion step
// immediately after.
const newText = workInProgress.pendingProps;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically didn't have to move this one but did this for consistency.
Can move it back too.

// and use tagName (which is upper case for HTML DOM elements). Or we could
// let the renderer "normalize" the fiber type so we don't have to read
// the type from DOM. However we need to remember SVG is case-sensitive.
var tag = domElement.tagName.toLowerCase();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is likely going to be an issue to do for every update. We might want to consider just banning non-lowercase uses instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can omit this change if it's better to discuss separately.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we already doing this in commitUpdate() before my PR. So I don't see why doing this during mounting is worse. (We should fix both?)

@sebmarkbage
Copy link
Collaborator

The creation order is only needed if we want to read the namespace from the DOM. We don't have to design it that way. We can pass a flag or something instead. It might be more efficient than touching DOM accessor methods anyway and doesn't lock us in to this creation order. (Even though we might want that anyway.)

Will review further today. The reason it is taking time is that I want to a deep review and consideration because this change is a compromise only for the purpose of letting users avoid switching renderers in the rare cases you switch modes. It is also very specific only to the DOM renderer. Anything that adds perf overhead or locks us into a particular creation phase seems like a big tradeoff.

@@ -517,22 +517,22 @@ describe('ReactIncrementalSideEffects', () => {
);
}
ReactNoop.render(<Foo tick={0} idx={0} />);
ReactNoop.flushDeferredPri(40 + 25);
ReactNoop.flushDeferredPri(40 + 20);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are all these taking more cycles?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Less cycles, isn't it? I'm not sure. Should I look into this?

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 had to wrap HostContext API into a closure so that it's parameterizeable with I and C.
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.
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 29, 2016

letting users avoid switching renderers in the rare cases you switch modes

I don’t understand this part. What case are you talking about?

This way createInstance() depends on the innermost container only for reading the namespace.
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 29, 2016

I’m looking into tracking namespaces separately so that we don’t need to change when we create.

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.
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 29, 2016

@sebmarkbage Should be ready for another review. Now only tracking the namespace, instances are created like before.

@sebmarkbage
Copy link
Collaborator

letting users avoid switching renderers in the rare cases you switch modes

I don’t understand this part. What case are you talking about?

I mean that we could also just make people write:

return <div>{ReactDOM.createSVGPortal(
  <svg>
     <path />
  </svg>
)}</div>;

I'm merely making the point that this whole thing is mostly just a convenience. Any additional cost in file size and performance needs to take that tradeoff into account.

But it seems like we can get it pretty small.

}
}
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
return null;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the foreignObject itself should be the SVG namespace but then get next namespace underneath it should be HTML.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add an assertion that the <svg /> tag itself has the SVG namespace?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shame on me!

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.
@sebmarkbage
Copy link
Collaborator

sebmarkbage commented Nov 29, 2016

This gave me an idea. Feel free to ignore if you're tired of exploring my whims :) and I can take a look myself.

  1. What if we tagged a new type of work HostContextComponent or something. If the type is a context provider, it gets tagged as such. That way we don't need to keep the stack of fibers around to know if we need to pop. HostContextComponent always pushes and pops. It also eliminates any extra work from the normal host component.

  2. What if just assumed that when you pop from MathML or SVG, you pop into HTML and when you pop from HTML you pop to SVG? That way we don't need a stack at all.

@gaearon
Copy link
Collaborator Author

gaearon commented Nov 29, 2016

I’ll try that. 😄

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.
@gaearon
Copy link
Collaborator Author

gaearon commented Nov 30, 2016

Closing as we decided to go with a cheaper approach.

@gaearon gaearon closed this Nov 30, 2016
@gaearon gaearon deleted the fiber-svg branch December 1, 2016 17:11
@gaearon gaearon mentioned this pull request Dec 1, 2016
@gaearon gaearon restored the fiber-svg branch December 2, 2016 23:34
@gaearon gaearon mentioned this pull request Dec 3, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants