@samitouri / QOS-React / commits / c87ffc0beb

[Fiber] Support SVG (#8490)

* 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

Dan Abramov committed Dec 8, 2016 at 13:10 UTC c87ffc0bebaabe69dfbd7b385480da614c5dc0da
13 files changed +797 -86
scripts/fiber/tests-passing.txt
+8
@@ -514,6 +514,12 @@ src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
514 * should render one portal
515 * should render many portals
516 * should render nested portals
517 +* should keep track of namespace across portals (simple)
518 +* should keep track of namespace across portals (medium)
519 +* should keep track of namespace across portals (complex)
520 +* should unwind namespaces on uncaught errors
521 +* should unwind namespaces on caught errors
522 +* should unwind namespaces on caught errors in a portal
523 * should pass portal context when rendering subtree elsewhere
524 * should update portal context if it changes due to setState
525 * should update portal context if it changes due to re-render
@@ -669,6 +675,8 @@ src/renderers/dom/shared/__tests__/ReactDOMInvalidARIAHook-test.js
675
676 src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
677 * creates initial namespaced markup
678 +* creates elements with SVG namespace inside SVG tag during mount
679 +* creates elements with SVG namespace inside SVG tag during update
680
681 src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
682 * updates a mounted text component in place
src/renderers/art/ReactARTFiber.js
+4
@@ -485,6 +485,10 @@ const ARTRenderer = ReactFiberReconciler({
485 // Noop
486 },
487
488 + getChildHostContext() {
489 + return null;
490 + },
491 +
492 scheduleAnimationCallback: window.requestAnimationFrame,
493
494 scheduleDeferredCallback: window.requestIdleCallback,
src/renderers/dom/fiber/ReactDOMFiber.js
+31 -12
@@ -33,6 +33,7 @@ var warning = require('warning');
33
34 var {
35 createElement,
36 + getChildNamespace,
37 setInitialProperties,
38 updateProperties,
39 } = ReactDOMFiberComponent;
@@ -60,6 +61,11 @@ let selectionInformation : ?mixed = null;
61
62 var DOMRenderer = ReactFiberReconciler({
63
64 + getChildHostContext(parentHostContext : string | null, type : string) {
65 + const parentNamespace = parentHostContext;
66 + return getChildNamespace(parentNamespace, type);
67 + },
68 +
69 prepareForCommit() : void {
70 eventsEnabled = ReactBrowserEventEmitter.isEnabled();
71 ReactBrowserEventEmitter.setEnabled(false);
@@ -76,11 +82,11 @@ var DOMRenderer = ReactFiberReconciler({
82 createInstance(
83 type : string,
84 props : Props,
79 - internalInstanceHandle : Object
85 + rootContainerInstance : Container,
86 + hostContext : string | null,
87 + internalInstanceHandle : Object,
88 ) : Instance {
81 - const root = document.documentElement; // HACK
82 -
83 - const domElement : Instance = createElement(type, props, root);
89 + const domElement : Instance = createElement(type, props, rootContainerInstance, hostContext);
90 precacheFiberNode(internalInstanceHandle, domElement);
91 return domElement;
92 },
@@ -89,10 +95,18 @@ var DOMRenderer = ReactFiberReconciler({
95 parentInstance.appendChild(child);
96 },
97
92 - finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
93 - const root = document.documentElement; // HACK
94 -
95 - setInitialProperties(domElement, type, props, root);
98 + finalizeInitialChildren(
99 + domElement : Instance,
100 + props : Props,
101 + rootContainerInstance : Container,
102 + ) : void {
103 + // TODO: we normalize here because DOM renderer expects tag to be lowercase.
104 + // We can change DOM renderer to compare special case against upper case,
105 + // and use tagName (which is upper case for HTML DOM elements). Or we could
106 + // let the renderer "normalize" the fiber type so we don't have to read
107 + // the type from DOM. However we need to remember SVG is case-sensitive.
108 + var tag = domElement.tagName.toLowerCase();
109 + setInitialProperties(domElement, tag, props, rootContainerInstance);
110 },
111
112 prepareUpdate(
@@ -107,14 +121,19 @@ var DOMRenderer = ReactFiberReconciler({
121 domElement : Instance,
122 oldProps : Props,
123 newProps : Props,
110 - internalInstanceHandle : Object
124 + rootContainerInstance : Container,
125 + internalInstanceHandle : Object,
126 ) : void {
112 - var type = domElement.tagName.toLowerCase(); // HACK
113 - var root = document.documentElement; // HACK
127 + // TODO: we normalize here because DOM renderer expects tag to be lowercase.
128 + // We can change DOM renderer to compare special case against upper case,
129 + // and use tagName (which is upper case for HTML DOM elements). Or we could
130 + // let the renderer "normalize" the fiber type so we don't have to read
131 + // the type from DOM. However we need to remember SVG is case-sensitive.
132 + var tag = domElement.tagName.toLowerCase();
133 // Update the internal instance handle so that we know which props are
134 // the current ones.
135 precacheFiberNode(internalInstanceHandle, domElement);
117 - updateProperties(domElement, type, oldProps, newProps, root);
136 + updateProperties(domElement, tag, oldProps, newProps, rootContainerInstance);
137 },
138
139 shouldSetTextContent(props : Props) : boolean {
src/renderers/dom/fiber/ReactDOMFiberComponent.js
+39 -30
@@ -46,6 +46,11 @@ var CHILDREN = 'children';
46 var STYLE = 'style';
47 var HTML = '__html';
48
49 +var {
50 + svg: SVG_NAMESPACE,
51 + mathml: MATH_NAMESPACE,
52 +} = DOMNamespaces;
53 +
54 // Node type for document fragments (Node.DOCUMENT_FRAGMENT_NODE).
55 var DOC_FRAGMENT_TYPE = 11;
56
@@ -451,45 +456,49 @@ function updateDOMProperties(
456 }
457 }
458
454 -var ReactDOMFiberComponent = {
459 +// Assumes there is no parent namespace.
460 +function getIntrinsicNamespace(type : string) : string | null {
461 + switch (type) {
462 + case 'svg':
463 + return SVG_NAMESPACE;
464 + case 'math':
465 + return MATH_NAMESPACE;
466 + default:
467 + return null;
468 + }
469 +}
470
456 - // TODO: Use this to keep track of changes to the host context and use this
457 - // to determine whether we switch to svg and back.
458 - // TODO: Does this need to check the current namespace? In case these tags
459 - // happen to be valid in some other namespace.
460 - isNewHostContainer(tag : string) {
461 - return tag === 'svg' || tag === 'foreignobject';
471 +var ReactDOMFiberComponent = {
472 + getChildNamespace(parentNamespace : string | null, type : string) : string | null {
473 + if (parentNamespace == null) {
474 + // No parent namespace: potential entry point.
475 + return getIntrinsicNamespace(type);
476 + }
477 + if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
478 + // We're leaving SVG.
479 + return null;
480 + }
481 + // By default, pass namespace below.
482 + return parentNamespace;
483 },
484
485 createElement(
465 - tag : string,
486 + type : string,
487 props : Object,
467 - rootContainerElement : Element
488 + rootContainerElement : Element,
489 + parentNamespace : string | null
490 ) : Element {
469 - validateDangerousTag(tag);
491 + validateDangerousTag(type);
492 // TODO:
471 - // tag.toLowerCase(); Do we need to apply lower case only on non-custom elements?
493 + // const tag = type.toLowerCase(); Do we need to apply lower case only on non-custom elements?
494
495 // We create tags in the namespace of their parent container, except HTML
496 // tags get no namespace.
475 - var namespaceURI = rootContainerElement.namespaceURI;
476 - if (namespaceURI == null ||
477 - namespaceURI === DOMNamespaces.svg &&
478 - rootContainerElement.tagName === 'foreignObject') {
479 - namespaceURI = DOMNamespaces.html;
480 - }
481 - if (namespaceURI === DOMNamespaces.html) {
482 - if (tag === 'svg') {
483 - namespaceURI = DOMNamespaces.svg;
484 - } else if (tag === 'math') {
485 - namespaceURI = DOMNamespaces.mathml;
486 - }
487 - // TODO: Make this a new root container element.
488 - }
489 -
497 var ownerDocument = rootContainerElement.ownerDocument;
498 var domElement : Element;
492 - if (namespaceURI === DOMNamespaces.html) {
499 + var namespaceURI = parentNamespace || getIntrinsicNamespace(type);
500 + if (namespaceURI == null) {
501 + const tag = type.toLowerCase();
502 if (tag === 'script') {
503 // Create the script via .innerHTML so its "parser-inserted" flag is
504 // set to true and it does not execute
@@ -499,17 +508,17 @@ var ReactDOMFiberComponent = {
508 var firstChild = ((div.firstChild : any) : HTMLScriptElement);
509 domElement = div.removeChild(firstChild);
510 } else if (props.is) {
502 - domElement = ownerDocument.createElement(tag, props.is);
511 + domElement = ownerDocument.createElement(type, props.is);
512 } else {
513 // Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
514 // See discussion in https://github.com/facebook/react/pull/6896
515 // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
507 - domElement = ownerDocument.createElement(tag);
516 + domElement = ownerDocument.createElement(type);
517 }
518 } else {
519 domElement = ownerDocument.createElementNS(
520 namespaceURI,
512 - tag
521 + type
522 );
523 }
524
src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js
+292
@@ -188,6 +188,39 @@ describe('ReactDOMFiber', () => {
188 }
189
190 if (ReactDOMFeatureFlags.useFiber) {
191 + var svgEls, htmlEls, mathEls;
192 + var expectSVG = {ref: el => svgEls.push(el)};
193 + var expectHTML = {ref: el => htmlEls.push(el)};
194 + var expectMath = {ref: el => mathEls.push(el)};
195 +
196 + var portal = function(tree) {
197 + return ReactDOM.unstable_createPortal(
198 + tree,
199 + document.createElement('div')
200 + );
201 + };
202 +
203 + var assertNamespacesMatch = function(tree) {
204 + container = document.createElement('div');
205 + svgEls = [];
206 + htmlEls = [];
207 + mathEls = [];
208 +
209 + ReactDOM.render(tree, container);
210 + svgEls.forEach(el => {
211 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
212 + });
213 + htmlEls.forEach(el => {
214 + expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
215 + });
216 + mathEls.forEach(el => {
217 + expect(el.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML');
218 + });
219 +
220 + ReactDOM.unmountComponentAtNode(container);
221 + expect(container.innerHTML).toBe('');
222 + };
223 +
224 it('should render one portal', () => {
225 var portalContainer = document.createElement('div');
226
@@ -333,6 +366,265 @@ describe('ReactDOMFiber', () => {
366 expect(container.innerHTML).toBe('');
367 });
368
369 + it('should keep track of namespace across portals (simple)', () => {
370 + assertNamespacesMatch(
371 + <svg {...expectSVG}>
372 + <image {...expectSVG} />
373 + {portal(
374 + <div {...expectHTML} />
375 + )}
376 + <image {...expectSVG} />
377 + </svg>
378 + );
379 + assertNamespacesMatch(
380 + <math {...expectMath}>
381 + <mi {...expectMath} />
382 + {portal(
383 + <div {...expectHTML} />
384 + )}
385 + <mi {...expectMath} />
386 + </math>
387 + );
388 + assertNamespacesMatch(
389 + <div {...expectHTML}>
390 + <p {...expectHTML} />
391 + {portal(
392 + <svg {...expectSVG}>
393 + <image {...expectSVG} />
394 + </svg>
395 + )}
396 + <p {...expectHTML} />
397 + </div>
398 + );
399 + });
400 +
401 + it('should keep track of namespace across portals (medium)', () => {
402 + assertNamespacesMatch(
403 + <div {...expectHTML}>
404 + <math {...expectMath}>
405 + <mi {...expectMath} />
406 + {portal(
407 + <svg {...expectSVG}>
408 + <image {...expectSVG} />
409 + </svg>
410 + )}
411 + </math>
412 + <p {...expectHTML} />
413 + </div>
414 + );
415 + assertNamespacesMatch(
416 + <math {...expectMath}>
417 + <mi {...expectMath} />
418 + {portal(
419 + <svg {...expectSVG}>
420 + <image {...expectSVG} />
421 + <foreignObject {...expectSVG}>
422 + <p {...expectHTML} />
423 + <math {...expectMath}>
424 + <mi {...expectMath} />
425 + </math>
426 + <p {...expectHTML} />
427 + </foreignObject>
428 + <image {...expectSVG} />
429 + </svg>
430 + )}
431 + <mi {...expectMath} />
432 + </math>
433 + );
434 + assertNamespacesMatch(
435 + <div {...expectHTML}>
436 + {portal(
437 + <svg {...expectSVG}>
438 + {portal(
439 + <div {...expectHTML} />
440 + )}
441 + <image {...expectSVG} />
442 + </svg>
443 + )}
444 + <p {...expectHTML} />
445 + </div>
446 + );
447 + assertNamespacesMatch(
448 + <svg {...expectSVG}>
449 + <svg {...expectSVG}>
450 + {portal(
451 + <div {...expectHTML} />
452 + )}
453 + <image {...expectSVG} />
454 + </svg>
455 + <image {...expectSVG} />
456 + </svg>
457 + );
458 + });
459 +
460 + it('should keep track of namespace across portals (complex)', () => {
461 + assertNamespacesMatch(
462 + <div {...expectHTML}>
463 + {portal(
464 + <svg {...expectSVG}>
465 + <image {...expectSVG} />
466 + </svg>
467 + )}
468 + <p {...expectHTML} />
469 + <svg {...expectSVG}>
470 + <image {...expectSVG} />
471 + </svg>
472 + <svg {...expectSVG}>
473 + <svg {...expectSVG}>
474 + <image {...expectSVG} />
475 + </svg>
476 + <image {...expectSVG} />
477 + </svg>
478 + <p {...expectHTML} />
479 + </div>
480 + );
481 + assertNamespacesMatch(
482 + <div {...expectHTML}>
483 + <svg {...expectSVG}>
484 + <svg {...expectSVG}>
485 + <image {...expectSVG} />
486 + {portal(
487 + <svg {...expectSVG}>
488 + <image {...expectSVG} />
489 + <svg {...expectSVG}>
490 + <image {...expectSVG} />
491 + </svg>
492 + <image {...expectSVG} />
493 + </svg>
494 + )}
495 + <image {...expectSVG} />
496 + <foreignObject {...expectSVG}>
497 + <p {...expectHTML} />
498 + {portal(<p {...expectHTML} />)}
499 + <p {...expectHTML} />
500 + </foreignObject>
501 + </svg>
502 + <image {...expectSVG} />
503 + </svg>
504 + <p {...expectHTML} />
505 + </div>
506 + );
507 + assertNamespacesMatch(
508 + <div {...expectHTML}>
509 + <svg {...expectSVG}>
510 + <foreignObject {...expectSVG}>
511 + <p {...expectHTML} />
512 + {portal(
513 + <svg {...expectSVG}>
514 + <image {...expectSVG} />
515 + <svg {...expectSVG}>
516 + <image {...expectSVG} />
517 + <foreignObject {...expectSVG}>
518 + <p {...expectHTML} />
519 + </foreignObject>
520 + {portal(<p {...expectHTML} />)}
521 + </svg>
522 + <image {...expectSVG} />
523 + </svg>
524 + )}
525 + <p {...expectHTML} />
526 + </foreignObject>
527 + <image {...expectSVG} />
528 + </svg>
529 + <p {...expectHTML} />
530 + </div>
531 + );
532 + });
533 +
534 + it('should unwind namespaces on uncaught errors', () => {
535 + function BrokenRender() {
536 + throw new Error('Hello');
537 + }
538 +
539 + expect(() => {
540 + assertNamespacesMatch(
541 + <svg {...expectSVG}>
542 + <BrokenRender />
543 + </svg>
544 + );
545 + }).toThrow('Hello');
546 + assertNamespacesMatch(
547 + <div {...expectHTML} />
548 + );
549 + });
550 +
551 + it('should unwind namespaces on caught errors', () => {
552 + function BrokenRender() {
553 + throw new Error('Hello');
554 + }
555 +
556 + class ErrorBoundary extends React.Component {
557 + state = {error: null};
558 + unstable_handleError(error) {
559 + this.setState({error});
560 + }
561 + render() {
562 + if (this.state.error) {
563 + return <p {...expectHTML} />;
564 + }
565 + return this.props.children;
566 + }
567 + }
568 +
569 + assertNamespacesMatch(
570 + <svg {...expectSVG}>
571 + <foreignObject {...expectSVG}>
572 + <ErrorBoundary>
573 + <math {...expectMath}>
574 + <BrokenRender />
575 + </math>
576 + </ErrorBoundary>
577 + </foreignObject>
578 + <image {...expectSVG} />
579 + </svg>
580 + );
581 + assertNamespacesMatch(
582 + <div {...expectHTML} />
583 + );
584 + });
585 +
586 + it('should unwind namespaces on caught errors in a portal', () => {
587 + function BrokenRender() {
588 + throw new Error('Hello');
589 + }
590 +
591 + class ErrorBoundary extends React.Component {
592 + state = {error: null};
593 + unstable_handleError(error) {
594 + this.setState({error});
595 + }
596 + render() {
597 + if (this.state.error) {
598 + return <image {...expectSVG} />;
599 + }
600 + return this.props.children;
601 + }
602 + }
603 +
604 + assertNamespacesMatch(
605 + <svg {...expectSVG}>
606 + <ErrorBoundary>
607 + {portal(
608 + <div {...expectHTML}>
609 + <math {...expectMath}>
610 + <BrokenRender />)
611 + </math>
612 + </div>
613 + )}
614 + </ErrorBoundary>
615 + {
616 + /*
617 + * TODO: enable. Currently this leads to stack overflow
618 + * but it might be a bug in error boundaries rather than SVG or portals.
619 + portal(
620 + <div {...expectHTML} />
621 + )
622 + */
623 + }
624 + </svg>
625 + );
626 + });
627 +
628 it('should pass portal context when rendering subtree elsewhere', () => {
629 var portalContainer = document.createElement('div');
630
src/renderers/dom/shared/__tests__/ReactDOMSVG-test.js
+127
@@ -12,12 +12,14 @@
12 'use strict';
13
14 var React;
15 +var ReactDOM;
16 var ReactDOMServer;
17
18 describe('ReactDOMSVG', () => {
19
20 beforeEach(() => {
21 React = require('React');
22 + ReactDOM = require('ReactDOM');
23 ReactDOMServer = require('ReactDOMServer');
24 });
25
@@ -30,4 +32,129 @@ describe('ReactDOMSVG', () => {
32 expect(markup).toContain('xlink:href="http://i.imgur.com/w7GCRPb.png"');
33 });
34
35 + it('creates elements with SVG namespace inside SVG tag during mount', () => {
36 + var node = document.createElement('div');
37 + var div, div2, div3, foreignObject, foreignObject2, g, image, image2, image3, p, svg, svg2, svg3, svg4;
38 + ReactDOM.render(
39 + <div>
40 + <svg ref={el => svg = el}>
41 + <g ref={el => g = el} strokeWidth="5">
42 + <svg ref={el => svg2 = el}>
43 + <foreignObject ref={el => foreignObject = el}>
44 + <svg ref={el => svg3 = el}>
45 + <svg ref={el => svg4 = el} />
46 + <image ref={el => image = el} xlinkHref="http://i.imgur.com/w7GCRPb.png" />
47 + </svg>
48 + <div ref={el => div = el} />
49 + </foreignObject>
50 + </svg>
51 + <image ref={el => image2 = el} xlinkHref="http://i.imgur.com/w7GCRPb.png" />
52 + <foreignObject ref={el => foreignObject2 = el}>
53 + <div ref={el => div2 = el} />
54 + </foreignObject>
55 + </g>
56 + </svg>
57 + <p ref={el => p = el}>
58 + <svg>
59 + <image ref={el => image3 = el} xlinkHref="http://i.imgur.com/w7GCRPb.png" />
60 + </svg>
61 + </p>
62 + <div ref={el => div3 = el} />
63 + </div>,
64 + node
65 + );
66 + [svg, svg2, svg3, svg4].forEach(el => {
67 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
68 + // SVG tagName is case sensitive.
69 + expect(el.tagName).toBe('svg');
70 + });
71 + expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
72 + expect(g.tagName).toBe('g');
73 + expect(g.getAttribute('stroke-width')).toBe('5');
74 + expect(p.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
75 + // DOM tagName is capitalized by browsers.
76 + expect(p.tagName).toBe('P');
77 + [image, image2, image3].forEach(el => {
78 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
79 + expect(el.tagName).toBe('image');
80 + expect(
81 + el.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
82 + ).toBe('http://i.imgur.com/w7GCRPb.png');
83 + });
84 + [foreignObject, foreignObject2].forEach(el => {
85 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
86 + expect(el.tagName).toBe('foreignObject');
87 + });
88 + [div, div2, div3].forEach(el => {
89 + expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
90 + expect(el.tagName).toBe('DIV');
91 + });
92 + });
93 +
94 + it('creates elements with SVG namespace inside SVG tag during update', () => {
95 + var inst, div, div2, foreignObject, foreignObject2, g, image, image2, svg, svg2, svg3, svg4;
96 +
97 + class App extends React.Component {
98 + state = {step: 0};
99 + render() {
100 + inst = this;
101 + const {step} = this.state;
102 + if (step === 0) {
103 + return null;
104 + }
105 + return (
106 + <g ref={el => g = el} strokeWidth="5">
107 + <svg ref={el => svg2 = el}>
108 + <foreignObject ref={el => foreignObject = el}>
109 + <svg ref={el => svg3 = el}>
110 + <svg ref={el => svg4 = el} />
111 + <image ref={el => image = el} xlinkHref="http://i.imgur.com/w7GCRPb.png" />
112 + </svg>
113 + <div ref={el => div = el} />
114 + </foreignObject>
115 + </svg>
116 + <image ref={el => image2 = el} xlinkHref="http://i.imgur.com/w7GCRPb.png" />
117 + <foreignObject ref={el => foreignObject2 = el}>
118 + <div ref={el => div2 = el} />
119 + </foreignObject>
120 + </g>
121 + );
122 + }
123 + }
124 +
125 + var node = document.createElement('div');
126 + ReactDOM.render(
127 + <svg ref={el => svg = el}>
128 + <App />
129 + </svg>,
130 + node
131 + );
132 + inst.setState({step: 1});
133 +
134 + [svg, svg2, svg3, svg4].forEach(el => {
135 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
136 + // SVG tagName is case sensitive.
137 + expect(el.tagName).toBe('svg');
138 + });
139 + expect(g.namespaceURI).toBe('http://www.w3.org/2000/svg');
140 + expect(g.tagName).toBe('g');
141 + expect(g.getAttribute('stroke-width')).toBe('5');
142 + [image, image2].forEach(el => {
143 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
144 + expect(el.tagName).toBe('image');
145 + expect(
146 + el.getAttributeNS('http://www.w3.org/1999/xlink', 'href')
147 + ).toBe('http://i.imgur.com/w7GCRPb.png');
148 + });
149 + [foreignObject, foreignObject2].forEach(el => {
150 + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg');
151 + expect(el.tagName).toBe('foreignObject');
152 + });
153 + [div, div2].forEach(el => {
154 + expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml');
155 + // DOM tagName is capitalized by browsers.
156 + expect(el.tagName).toBe('DIV');
157 + });
158 + });
159 +
160 });
src/renderers/noop/ReactNoop.js
+5 -1
@@ -40,6 +40,10 @@ var instanceCounter = 0;
40
41 var NoopRenderer = ReactFiberReconciler({
42
43 + getChildHostContext() {
44 + return null;
45 + },
46 +
47 createInstance(type : string, props : Props) : Instance {
48 const inst = {
49 id: instanceCounter++,
@@ -56,7 +60,7 @@ var NoopRenderer = ReactFiberReconciler({
60 parentInstance.children.push(child);
61 },
62
59 - finalizeInitialChildren(domElement : Instance, type : string, props : Props) : void {
63 + finalizeInitialChildren(domElement : Instance, props : Props) : void {
64 // Noop
65 },
66
src/renderers/shared/fiber/ReactFiberBeginWork.js
+36 -7
@@ -14,6 +14,7 @@
14
15 import type { ReactCoroutine } from 'ReactCoroutine';
16 import type { Fiber } from 'ReactFiber';
17 +import type { HostContext } from 'ReactFiberHostContext';
18 import type { FiberRoot } from 'ReactFiberRoot';
19 import type { HostConfig } from 'ReactFiberReconciler';
20 import type { PriorityLevel } from 'ReactPriorityLevel';
@@ -59,13 +60,20 @@ var {
60 var ReactCurrentOwner = require('ReactCurrentOwner');
61 var ReactFiberClassComponent = require('ReactFiberClassComponent');
62
62 -module.exports = function<T, P, I, TI, C>(
63 - config : HostConfig<T, P, I, TI, C>,
63 +module.exports = function<T, P, I, TI, C, CX>(
64 + config : HostConfig<T, P, I, TI, C, CX>,
65 + hostContext : HostContext<C, CX>,
66 scheduleUpdate : (fiber: Fiber) => void
67 ) {
68
69 const { shouldSetTextContent } = config;
70
71 + const {
72 + pushHostContext,
73 + pushHostContainer,
74 + resetHostContainer,
75 + } = hostContext;
76 +
77 const {
78 adoptClassInstance,
79 constructClassInstance,
@@ -233,7 +241,6 @@ module.exports = function<T, P, I, TI, C>(
241 // empty, we need to schedule the text content to be reset.
242 workInProgress.effectTag |= ContentReset;
243 }
236 -
244 if (nextProps.hidden &&
245 workInProgress.pendingWorkPriority !== OffscreenPriority) {
246 // If this host component is hidden, we can bail out on the children.
@@ -270,6 +277,7 @@ module.exports = function<T, P, I, TI, C>(
277 // Abort and don't process children yet.
278 return null;
279 } else {
280 + pushHostContext(workInProgress);
281 reconcileChildren(current, workInProgress, nextChildren);
282 return workInProgress.child;
283 }
@@ -357,8 +365,9 @@ module.exports = function<T, P, I, TI, C>(
365
366 function bailoutOnAlreadyFinishedWork(current, workInProgress : Fiber) : ?Fiber {
367 const priorityLevel = workInProgress.pendingWorkPriority;
368 + const isHostComponent = workInProgress.tag === HostComponent;
369
361 - if (workInProgress.tag === HostComponent &&
370 + if (isHostComponent &&
371 workInProgress.memoizedProps.hidden &&
372 workInProgress.pendingWorkPriority !== OffscreenPriority) {
373 // This subtree still has work, but it should be deprioritized so we need
@@ -400,14 +409,32 @@ module.exports = function<T, P, I, TI, C>(
409
410 cloneChildFibers(current, workInProgress);
411 markChildAsProgressed(current, workInProgress, priorityLevel);
412 +
413 // Put context on the stack because we will work on children
404 - if (isContextProvider(workInProgress)) {
405 - pushContextProvider(workInProgress, false);
414 + if (isHostComponent) {
415 + pushHostContext(workInProgress);
416 + } else {
417 + switch (workInProgress.tag) {
418 + case ClassComponent:
419 + if (isContextProvider(workInProgress)) {
420 + pushContextProvider(workInProgress, false);
421 + }
422 + break;
423 + case HostRoot:
424 + case HostPortal:
425 + pushHostContainer(workInProgress.stateNode.containerInfo);
426 + break;
427 + }
428 }
429 + // TODO: this is annoyingly duplicating non-jump codepaths.
430 +
431 return workInProgress.child;
432 }
433
434 function bailoutOnLowPriority(current, workInProgress) {
435 + if (workInProgress.tag === HostPortal) {
436 + pushHostContainer(workInProgress.stateNode.containerInfo);
437 + }
438 // TODO: What if this is currently in progress?
439 // How can that happen? How is this not being cloned?
440 return null;
@@ -417,6 +444,7 @@ module.exports = function<T, P, I, TI, C>(
444 if (!workInProgress.return) {
445 // Don't start new work with context on the stack.
446 resetContext();
447 + resetHostContainer();
448 }
449
450 if (workInProgress.pendingWorkPriority === NoWork ||
@@ -461,6 +489,7 @@ module.exports = function<T, P, I, TI, C>(
489 } else {
490 pushTopLevelContextObject(root.context, false);
491 }
492 + pushHostContainer(workInProgress.stateNode.containerInfo);
493 reconcileChildren(current, workInProgress, workInProgress.pendingProps);
494 // A yield component is just a placeholder, we can just run through the
495 // next one immediately.
@@ -486,8 +515,8 @@ module.exports = function<T, P, I, TI, C>(
515 // next one immediately.
516 return null;
517 case HostPortal:
518 + pushHostContainer(workInProgress.stateNode.containerInfo);
519 updatePortalComponent(current, workInProgress);
490 - // TODO: is this right?
520 return workInProgress.child;
521 case Fragment:
522 updateFragment(current, workInProgress);
src/renderers/shared/fiber/ReactFiberCommitWork.js
+17 -9
@@ -13,6 +13,7 @@
13 'use strict';
14
15 import type { Fiber } from 'ReactFiber';
16 +import type { HostContext } from 'ReactFiberHostContext';
17 import type { HostConfig } from 'ReactFiberReconciler';
18
19 var ReactTypeOfWork = require('ReactTypeOfWork');
@@ -33,18 +34,24 @@ var {
34 ContentReset,
35 } = require('ReactTypeOfSideEffect');
36
36 -module.exports = function<T, P, I, TI, C>(
37 - config : HostConfig<T, P, I, TI, C>,
37 +module.exports = function<T, P, I, TI, C, CX>(
38 + config : HostConfig<T, P, I, TI, C, CX>,
39 + hostContext : HostContext<C, CX>,
40 captureError : (failedFiber : Fiber, error: Error) => ?Fiber
41 ) {
42
41 - const commitUpdate = config.commitUpdate;
42 - const resetTextContent = config.resetTextContent;
43 - const commitTextUpdate = config.commitTextUpdate;
43 + const {
44 + commitUpdate,
45 + resetTextContent,
46 + commitTextUpdate,
47 + appendChild,
48 + insertBefore,
49 + removeChild,
50 + } = config;
51
45 - const appendChild = config.appendChild;
46 - const insertBefore = config.insertBefore;
47 - const removeChild = config.removeChild;
52 + const {
53 + getRootHostContainer,
54 + } = hostContext;
55
56 // Capture errors so they don't interrupt unmounting.
57 function safelyCallComponentWillUnmount(current, instance) {
@@ -354,7 +361,8 @@ module.exports = function<T, P, I, TI, C>(
361 // Commit the work prepared earlier.
362 const newProps = finishedWork.memoizedProps;
363 const oldProps = current.memoizedProps;
357 - commitUpdate(instance, oldProps, newProps, finishedWork);
364 + const rootContainerInstance = getRootHostContainer();
365 + commitUpdate(instance, oldProps, newProps, rootContainerInstance, finishedWork);
366 }
367 detachRefIfNeeded(current, finishedWork);
368 return;
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+30 -9
@@ -14,6 +14,7 @@
14
15 import type { ReactCoroutine } from 'ReactCoroutine';
16 import type { Fiber } from 'ReactFiber';
17 +import type { HostContext } from 'ReactFiberHostContext';
18 import type { FiberRoot } from 'ReactFiberRoot';
19 import type { HostConfig } from 'ReactFiberReconciler';
20 import type { ReifiedYield } from 'ReactReifiedYield';
@@ -43,13 +44,24 @@ var {
44 Callback,
45 } = ReactTypeOfSideEffect;
46
46 -module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
47 +module.exports = function<T, P, I, TI, C, CX>(
48 + config : HostConfig<T, P, I, TI, C, CX>,
49 + hostContext : HostContext<C, CX>,
50 +) {
51 + const {
52 + createInstance,
53 + createTextInstance,
54 + appendInitialChild,
55 + finalizeInitialChildren,
56 + prepareUpdate,
57 + } = config;
58
48 - const createInstance = config.createInstance;
49 - const appendInitialChild = config.appendInitialChild;
50 - const finalizeInitialChildren = config.finalizeInitialChildren;
51 - const createTextInstance = config.createTextInstance;
52 - const prepareUpdate = config.prepareUpdate;
59 + const {
60 + getRootHostContainer,
61 + popHostContext,
62 + getHostContext,
63 + popHostContainer,
64 + } = hostContext;
65
66 function markUpdate(workInProgress : Fiber) {
67 // Tag the fiber with an update effect. This turns a Placement into
@@ -202,6 +214,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
214 return null;
215 }
216 case HostComponent:
217 + popHostContext(workInProgress);
218 let newProps = workInProgress.pendingProps;
219 if (current && workInProgress.stateNode != null) {
220 // If we have an alternate, that means this is an update and we need to
@@ -229,14 +242,21 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
242 }
243 }
244
245 + const rootContainerInstance = getRootHostContainer();
246 + const currentHostContext = getHostContext();
247 // TODO: Move createInstance to beginWork and keep it on a context
248 // "stack" as the parent. Then append children as we go in beginWork
249 // or completeWork depending on we want to add then top->down or
250 // bottom->up. Top->down is faster in IE11.
236 - // Finally, finalizeInitialChildren here in completeWork.
237 - const instance = createInstance(workInProgress.type, newProps, workInProgress);
251 + const instance = createInstance(
252 + workInProgress.type,
253 + newProps,
254 + rootContainerInstance,
255 + currentHostContext,
256 + workInProgress
257 + );
258 appendAllChildren(instance, workInProgress);
239 - finalizeInitialChildren(instance, workInProgress.type, newProps);
259 + finalizeInitialChildren(instance, newProps, rootContainerInstance);
260
261 workInProgress.stateNode = instance;
262 if (workInProgress.ref) {
@@ -294,6 +314,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
314 // TODO: Only mark this as an update if we have any pending callbacks.
315 markUpdate(workInProgress);
316 workInProgress.memoizedProps = workInProgress.pendingProps;
317 + popHostContainer();
318 return null;
319
320 // Error cases
src/renderers/shared/fiber/ReactFiberHostContext.js new
+157
@@ -0,0 +1,157 @@
1 +/**
2 + * Copyright 2013-present, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @providesModule ReactFiberHostContext
10 + * @flow
11 + */
12 +
13 +'use strict';
14 +
15 +import type { Fiber } from 'ReactFiber';
16 +import type { HostConfig } from 'ReactFiberReconciler';
17 +
18 +export type HostContext<C, CX> = {
19 + getRootHostContainer() : C,
20 + getHostContext() : CX | null,
21 +
22 + pushHostContext(fiber : Fiber) : void,
23 + popHostContext(fiber : Fiber) : void,
24 +
25 + pushHostContainer(container : C) : void,
26 + popHostContainer() : void,
27 + resetHostContainer() : void,
28 +};
29 +
30 +module.exports = function<T, P, I, TI, C, CX>(
31 + config : HostConfig<T, P, I, TI, C, CX>
32 +) : HostContext<C, CX> {
33 + const {
34 + getChildHostContext,
35 + } = config;
36 +
37 + type ContainerState = {
38 + rootInstance : C,
39 + contextFibers : Array<Fiber | null> | null,
40 + contextValues : Array<CX | null> | null,
41 + contextDepth: number
42 + };
43 +
44 + function createContainerState(rootInstance) {
45 + return {
46 + rootInstance,
47 + contextFibers: null,
48 + contextValues: null,
49 + contextDepth: -1,
50 + };
51 + }
52 +
53 + // If we meet any portals, we'll go deeper.
54 + let containerStack : Array<ContainerState> = [];
55 + let containerDepth : number = -1;
56 +
57 + function getRootHostContainer() : C {
58 + if (containerDepth === -1) {
59 + throw new Error('Expected to find a root container.');
60 + }
61 + const containerState = containerStack[containerDepth];
62 + const {rootInstance} = containerState;
63 + return rootInstance;
64 + }
65 +
66 + function pushHostContainer(portalHostContainer) {
67 + containerDepth++;
68 + if (containerDepth === containerStack.length) {
69 + containerStack[containerDepth] = createContainerState(portalHostContainer);
70 + } else {
71 + const containerState = containerStack[containerDepth];
72 + containerState.rootInstance = portalHostContainer;
73 + containerState.contextFibers = null;
74 + containerState.contextValues = null;
75 + containerState.contextDepth = -1;
76 + }
77 + }
78 +
79 + function popHostContainer() {
80 + if (containerDepth === -1) {
81 + throw new Error('Already reached the root.');
82 + }
83 + containerDepth--;
84 + }
85 +
86 + function getHostContext() : CX | null {
87 + if (containerDepth == null) {
88 + throw new Error('Expected to find a root container.');
89 + }
90 + const containerState = containerStack[containerDepth];
91 + const {contextDepth, contextValues} = containerState;
92 + if (contextDepth === -1) {
93 + return null;
94 + }
95 + if (contextValues == null) {
96 + throw new Error('Expected context values to exist.');
97 + }
98 + return contextValues[contextDepth];
99 + }
100 +
101 + function pushHostContext(fiber : Fiber) : void {
102 + const parentHostContext = getHostContext();
103 + const currentHostContext = getChildHostContext(parentHostContext, fiber.type);
104 + if (parentHostContext === currentHostContext) {
105 + return;
106 + }
107 + const containerState = containerStack[containerDepth];
108 + let {contextDepth, contextFibers, contextValues} = containerState;
109 + if (contextFibers == null) {
110 + contextFibers = [];
111 + containerState.contextFibers = contextFibers;
112 + }
113 + if (contextValues == null) {
114 + contextValues = [];
115 + containerState.contextValues = contextValues;
116 + }
117 + contextDepth++;
118 + containerState.contextDepth = contextDepth;
119 + contextFibers[contextDepth] = fiber;
120 + contextValues[contextDepth] = currentHostContext;
121 + }
122 +
123 + function popHostContext(fiber : Fiber) : void {
124 + const containerState = containerStack[containerDepth];
125 + let {contextDepth} = containerState;
126 + if (contextDepth === -1) {
127 + return;
128 + }
129 + const {contextFibers, contextValues} = containerState;
130 + if (contextFibers == null || contextValues == null) {
131 + throw new Error('Expected host context stacks to exist when index is more than -1.');
132 + }
133 + if (fiber !== contextFibers[contextDepth]) {
134 + return;
135 + }
136 + contextFibers[contextDepth] = null;
137 + contextValues[contextDepth] = null;
138 + contextDepth--;
139 + containerState.contextDepth = contextDepth;
140 + }
141 +
142 + function resetHostContainer() {
143 + containerDepth = -1;
144 + }
145 +
146 + return {
147 + getRootHostContainer,
148 + getHostContext,
149 +
150 + pushHostContext,
151 + popHostContext,
152 +
153 + pushHostContainer,
154 + popHostContainer,
155 + resetHostContainer,
156 + };
157 +};
src/renderers/shared/fiber/ReactFiberReconciler.js
+8 -6
@@ -40,14 +40,16 @@ export type Deadline = {
40
41 type OpaqueNode = Fiber;
42
43 -export type HostConfig<T, P, I, TI, C> = {
43 +export type HostConfig<T, P, I, TI, C, CX> = {
44
45 - createInstance(type : T, props : P, internalInstanceHandle : OpaqueNode) : I,
46 - appendInitialChild(parentInstance : I, child : I) : void,
47 - finalizeInitialChildren(parentInstance : I, type : T, props : P) : void,
45 + getChildHostContext(parentHostContext : CX | null, type : T) : CX,
46 +
47 + createInstance(type : T, props : P, rootContainerInstance : C, hostContext : CX | null, internalInstanceHandle : OpaqueNode) : I,
48 + appendInitialChild(parentInstance : I, child : I | TI) : void,
49 + finalizeInitialChildren(parentInstance : I, props : P, rootContainerInstance : C) : void,
50
51 prepareUpdate(instance : I, oldProps : P, newProps : P) : boolean,
50 - commitUpdate(instance : I, oldProps : P, newProps : P, internalInstanceHandle : OpaqueNode) : void,
52 + commitUpdate(instance : I, oldProps : P, newProps : P, rootContainerInstance : C, internalInstanceHandle : OpaqueNode) : void,
53
54 shouldSetTextContent(props : P) : boolean,
55 resetTextContent(instance : I) : void,
@@ -93,7 +95,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
95 parentContext;
96 });
97
96 -module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) : Reconciler<C, I, TI> {
98 +module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
99
100 var {
101 scheduleWork,
src/renderers/shared/fiber/ReactFiberScheduler.js
+43 -12
@@ -20,6 +20,7 @@ import type { PriorityLevel } from 'ReactPriorityLevel';
20 var ReactFiberBeginWork = require('ReactFiberBeginWork');
21 var ReactFiberCompleteWork = require('ReactFiberCompleteWork');
22 var ReactFiberCommitWork = require('ReactFiberCommitWork');
23 +var ReactFiberHostContext = require('ReactFiberHostContext');
24 var ReactCurrentOwner = require('ReactCurrentOwner');
25
26 var { cloneFiber } = require('ReactFiber');
@@ -47,6 +48,8 @@ var {
48
49 var {
50 HostRoot,
51 + HostComponent,
52 + HostPortal,
53 ClassComponent,
54 } = require('ReactTypeOfWork');
55
@@ -60,19 +63,25 @@ if (__DEV__) {
63
64 var timeHeuristicForUnitOfWork = 1;
65
63 -module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
66 +module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
67 + const hostContext = ReactFiberHostContext(config);
68 + const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
69 const { beginWork, beginFailedWork } =
65 - ReactFiberBeginWork(config, scheduleUpdate);
66 - const { completeWork } = ReactFiberCompleteWork(config);
67 - const { commitPlacement, commitDeletion, commitWork, commitLifeCycles } =
68 - ReactFiberCommitWork(config, captureError);
69 -
70 - const hostScheduleAnimationCallback = config.scheduleAnimationCallback;
71 - const hostScheduleDeferredCallback = config.scheduleDeferredCallback;
72 - const useSyncScheduling = config.useSyncScheduling;
73 -
74 - const prepareForCommit = config.prepareForCommit;
75 - const resetAfterCommit = config.resetAfterCommit;
70 + ReactFiberBeginWork(config, hostContext, scheduleUpdate);
71 + const { completeWork } = ReactFiberCompleteWork(config, hostContext);
72 + const {
73 + commitPlacement,
74 + commitDeletion,
75 + commitWork,
76 + commitLifeCycles,
77 + } = ReactFiberCommitWork(config, hostContext, captureError);
78 + const {
79 + scheduleAnimationCallback: hostScheduleAnimationCallback,
80 + scheduleDeferredCallback: hostScheduleDeferredCallback,
81 + useSyncScheduling,
82 + prepareForCommit,
83 + resetAfterCommit,
84 + } = config;
85
86 // The priority level to use when scheduling an update.
87 let priorityContext : PriorityLevel = useSyncScheduling ?
@@ -309,6 +318,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
318 }
319
320 resetAfterCommit();
321 + // We didn't pop the host root in the complete phase because we still needed
322 + // it for the commitUpdate() calls, but now we can reset host context.
323 + resetHostContainer();
324
325 // In the second pass we'll perform all life-cycles and ref callbacks.
326 // Life-cycles happen as a separate pass so that all placements, updates,
@@ -658,6 +670,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
670 // props, the nodes higher up in the tree will rerender unnecessarily.
671 if (failedWork) {
672 unwindContext(failedWork, boundary);
673 + unwindHostContext(failedWork, boundary);
674 }
675 nextUnitOfWork = completeUnitOfWork(boundary);
676 }
@@ -864,6 +877,24 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
877 }
878 }
879
880 + function unwindHostContext(from : Fiber, to: Fiber) {
881 + let node = from;
882 + while (node && (node !== to) && (node.alternate !== to)) {
883 + switch (node.tag) {
884 + case HostComponent:
885 + popHostContext(node);
886 + break;
887 + case HostRoot:
888 + popHostContainer();
889 + break;
890 + case HostPortal:
891 + popHostContainer();
892 + break;
893 + }
894 + node = node.return;
895 + }
896 + }
897 +
898 function scheduleWork(root : FiberRoot) {
899 let priorityLevel = priorityContext;
900