2018-05-15 08:12:28 +08:00
|
|
|
/**
|
2018-09-08 06:11:23 +08:00
|
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
2018-05-15 08:12:28 +08:00
|
|
|
*
|
|
|
|
|
* This source code is licensed under the MIT license found in the
|
|
|
|
|
* LICENSE file in the root directory of this source tree.
|
|
|
|
|
*
|
|
|
|
|
* @flow
|
|
|
|
|
*/
|
|
|
|
|
|
2019-07-20 05:20:28 +08:00
|
|
|
import type {
|
2019-07-24 06:46:44 +08:00
|
|
|
ReactEventResponder,
|
|
|
|
|
ReactEventResponderInstance,
|
2019-07-20 05:20:28 +08:00
|
|
|
ReactFundamentalComponentInstance,
|
|
|
|
|
} from 'shared/ReactTypes';
|
2019-03-20 19:20:17 +08:00
|
|
|
|
2019-12-18 18:24:46 +08:00
|
|
|
import {enableDeprecatedFlareAPI} from 'shared/ReactFeatureFlags';
|
2020-04-07 08:17:27 +08:00
|
|
|
import {REACT_OPAQUE_ID_TYPE} from 'shared/ReactSymbols';
|
2019-03-15 01:02:42 +08:00
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export type Type = string;
|
|
|
|
|
export type Props = Object;
|
|
|
|
|
export type Container = {|
|
|
|
|
|
children: Array<Instance | TextInstance>,
|
|
|
|
|
createNodeMock: Function,
|
|
|
|
|
tag: 'CONTAINER',
|
|
|
|
|
|};
|
2018-05-15 08:12:28 +08:00
|
|
|
export type Instance = {|
|
|
|
|
|
type: string,
|
|
|
|
|
props: Object,
|
Hide timed-out children instead of deleting them so their state is preserved (#13823)
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
|
|
|
isHidden: boolean,
|
2018-05-15 08:12:28 +08:00
|
|
|
children: Array<Instance | TextInstance>,
|
2019-10-09 01:32:53 +08:00
|
|
|
internalInstanceHandle: Object,
|
2018-05-15 08:12:28 +08:00
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
tag: 'INSTANCE',
|
|
|
|
|
|};
|
|
|
|
|
export type TextInstance = {|
|
|
|
|
|
text: string,
|
Hide timed-out children instead of deleting them so their state is preserved (#13823)
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
|
|
|
isHidden: boolean,
|
2018-05-15 08:12:28 +08:00
|
|
|
tag: 'TEXT',
|
|
|
|
|
|};
|
2018-05-19 18:29:11 +08:00
|
|
|
export type HydratableInstance = Instance | TextInstance;
|
|
|
|
|
export type PublicInstance = Instance | TextInstance;
|
|
|
|
|
export type HostContext = Object;
|
|
|
|
|
export type UpdatePayload = Object;
|
|
|
|
|
export type ChildSet = void; // Unused
|
2018-07-04 10:22:41 +08:00
|
|
|
export type TimeoutHandle = TimeoutID;
|
|
|
|
|
export type NoTimeout = -1;
|
2019-06-19 06:41:00 +08:00
|
|
|
export type EventResponder = any;
|
2020-04-07 08:17:27 +08:00
|
|
|
export opaque type OpaqueIDType =
|
|
|
|
|
| string
|
|
|
|
|
| {
|
|
|
|
|
toString: () => string | void,
|
|
|
|
|
valueOf: () => string | void,
|
|
|
|
|
};
|
2018-05-15 08:12:28 +08:00
|
|
|
|
2020-03-11 04:31:12 +08:00
|
|
|
export type ReactListenerEvent = Object;
|
|
|
|
|
export type ReactListenerMap = Object;
|
|
|
|
|
export type ReactListener = Object;
|
2020-03-31 03:42:41 +08:00
|
|
|
export type RendererInspectionConfig = $ReadOnly<{||}>;
|
2020-03-11 04:31:12 +08:00
|
|
|
|
2020-03-22 06:22:01 +08:00
|
|
|
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoPersistence';
|
|
|
|
|
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
|
2018-05-19 18:29:11 +08:00
|
|
|
|
2019-03-20 19:20:17 +08:00
|
|
|
const EVENT_COMPONENT_CONTEXT = {};
|
Inline fbjs/lib/emptyObject (#13055)
* Inline fbjs/lib/emptyObject
* Explicit naming
* Compare to undefined
* Another approach for detecting whether we can mutate
Each renderer would have its own local LegacyRefsObject function.
While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.
If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.
* Clearer naming
* Add test case for strings refs across renderers
* Use a shared empty object for refs by reading it from React
* Remove string refs from ReactART test
It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).
This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).
For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.
* Simplify the condition
2018-06-19 20:41:42 +08:00
|
|
|
const NO_CONTEXT = {};
|
|
|
|
|
const UPDATE_SIGNAL = {};
|
2019-10-11 22:58:27 +08:00
|
|
|
const nodeToInstanceMap = new WeakMap();
|
|
|
|
|
|
Inline fbjs/lib/emptyObject (#13055)
* Inline fbjs/lib/emptyObject
* Explicit naming
* Compare to undefined
* Another approach for detecting whether we can mutate
Each renderer would have its own local LegacyRefsObject function.
While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.
If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.
* Clearer naming
* Add test case for strings refs across renderers
* Use a shared empty object for refs by reading it from React
* Remove string refs from ReactART test
It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).
This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).
For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.
* Simplify the condition
2018-06-19 20:41:42 +08:00
|
|
|
if (__DEV__) {
|
|
|
|
|
Object.freeze(NO_CONTEXT);
|
|
|
|
|
Object.freeze(UPDATE_SIGNAL);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function getPublicInstance(inst: Instance | TextInstance): * {
|
2018-05-15 08:12:28 +08:00
|
|
|
switch (inst.tag) {
|
|
|
|
|
case 'INSTANCE':
|
|
|
|
|
const createNodeMock = inst.rootContainerInstance.createNodeMock;
|
2019-10-11 22:58:27 +08:00
|
|
|
const mockNode = createNodeMock({
|
2018-05-15 08:12:28 +08:00
|
|
|
type: inst.type,
|
|
|
|
|
props: inst.props,
|
|
|
|
|
});
|
2019-10-11 22:58:27 +08:00
|
|
|
if (typeof mockNode === 'object' && mockNode !== null) {
|
|
|
|
|
nodeToInstanceMap.set(mockNode, inst);
|
|
|
|
|
}
|
|
|
|
|
return mockNode;
|
2018-05-15 08:12:28 +08:00
|
|
|
default:
|
|
|
|
|
return inst;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function appendChild(
|
2018-05-15 08:12:28 +08:00
|
|
|
parentInstance: Instance | Container,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
): void {
|
2018-08-21 01:03:22 +08:00
|
|
|
if (__DEV__) {
|
2019-12-11 11:28:14 +08:00
|
|
|
if (!Array.isArray(parentInstance.children)) {
|
2019-12-15 02:09:25 +08:00
|
|
|
console.error(
|
2019-12-11 11:28:14 +08:00
|
|
|
'An invalid container has been provided. ' +
|
|
|
|
|
'This may indicate that another renderer is being used in addition to the test renderer. ' +
|
|
|
|
|
'(For example, ReactDOM.createPortal inside of a ReactTestRenderer tree.) ' +
|
|
|
|
|
'This is not supported.',
|
|
|
|
|
);
|
|
|
|
|
}
|
2018-08-21 01:03:22 +08:00
|
|
|
}
|
2018-05-15 08:12:28 +08:00
|
|
|
const index = parentInstance.children.indexOf(child);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
parentInstance.children.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
parentInstance.children.push(child);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function insertBefore(
|
2018-05-15 08:12:28 +08:00
|
|
|
parentInstance: Instance | Container,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
beforeChild: Instance | TextInstance,
|
|
|
|
|
): void {
|
|
|
|
|
const index = parentInstance.children.indexOf(child);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
parentInstance.children.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
const beforeIndex = parentInstance.children.indexOf(beforeChild);
|
|
|
|
|
parentInstance.children.splice(beforeIndex, 0, child);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function removeChild(
|
2018-05-15 08:12:28 +08:00
|
|
|
parentInstance: Instance | Container,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
): void {
|
|
|
|
|
const index = parentInstance.children.indexOf(child);
|
|
|
|
|
parentInstance.children.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
export function getRootHostContext(
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
): HostContext {
|
Inline fbjs/lib/emptyObject (#13055)
* Inline fbjs/lib/emptyObject
* Explicit naming
* Compare to undefined
* Another approach for detecting whether we can mutate
Each renderer would have its own local LegacyRefsObject function.
While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.
If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.
* Clearer naming
* Add test case for strings refs across renderers
* Use a shared empty object for refs by reading it from React
* Remove string refs from ReactART test
It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).
This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).
For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.
* Simplify the condition
2018-06-19 20:41:42 +08:00
|
|
|
return NO_CONTEXT;
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getChildHostContext(
|
|
|
|
|
parentHostContext: HostContext,
|
|
|
|
|
type: string,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
): HostContext {
|
Inline fbjs/lib/emptyObject (#13055)
* Inline fbjs/lib/emptyObject
* Explicit naming
* Compare to undefined
* Another approach for detecting whether we can mutate
Each renderer would have its own local LegacyRefsObject function.
While in general we don't want `instanceof`, here it lets us do a simple check: did *we* create the refs object?
Then we can mutate it.
If the check didn't pass, either we're attaching ref for the first time (so we know to use the constructor),
or (unlikely) we're attaching a ref to a component owned by another renderer. In this case, to avoid "losing"
refs, we assign them onto the new object. Even in that case it shouldn't "hop" between renderers anymore.
* Clearer naming
* Add test case for strings refs across renderers
* Use a shared empty object for refs by reading it from React
* Remove string refs from ReactART test
It's not currently possible to resetModules() between several renderers
without also resetting the `React` module. However, that leads to losing
the referential identity of the empty ref object, and thus subsequent
checks in the renderers for whether it is pooled fail (and cause assignments
to a frozen object).
This has always been the case, but we used to work around it by shimming
fbjs/lib/emptyObject in tests and preserving its referential identity.
This won't work anymore because we've inlined it. And preserving referential
identity of React itself wouldn't be great because it could be confusing during
testing (although we might want to revisit this in the future by moving its
stateful parts into a separate package).
For now, I'm removing string ref usage from this test because only this is
the only place in our tests where we hit this problem, and it's only
related to string refs, and not just ref mechanism in general.
* Simplify the condition
2018-06-19 20:41:42 +08:00
|
|
|
return NO_CONTEXT;
|
2018-05-19 18:29:11 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function prepareForCommit(containerInfo: Container): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function resetAfterCommit(containerInfo: Container): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createInstance(
|
|
|
|
|
type: string,
|
|
|
|
|
props: Props,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: Object,
|
|
|
|
|
internalInstanceHandle: Object,
|
|
|
|
|
): Instance {
|
2019-07-24 18:31:33 +08:00
|
|
|
let propsToUse = props;
|
2019-12-18 18:24:46 +08:00
|
|
|
if (enableDeprecatedFlareAPI) {
|
2019-11-18 21:32:50 +08:00
|
|
|
if (props.DEPRECATED_flareListeners != null) {
|
|
|
|
|
// We want to remove the "DEPRECATED_flareListeners" prop
|
2019-08-02 02:08:54 +08:00
|
|
|
// as we don't want it in the test renderer's
|
|
|
|
|
// instance props.
|
2019-11-18 21:32:50 +08:00
|
|
|
const {DEPRECATED_flareListeners, ...otherProps} = props; // eslint-disable-line
|
2019-07-24 18:31:33 +08:00
|
|
|
propsToUse = otherProps;
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-05-19 18:29:11 +08:00
|
|
|
return {
|
|
|
|
|
type,
|
2019-07-24 18:31:33 +08:00
|
|
|
props: propsToUse,
|
Hide timed-out children instead of deleting them so their state is preserved (#13823)
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
|
|
|
isHidden: false,
|
2018-05-19 18:29:11 +08:00
|
|
|
children: [],
|
2019-10-09 01:32:53 +08:00
|
|
|
internalInstanceHandle,
|
2018-05-19 18:29:11 +08:00
|
|
|
rootContainerInstance,
|
|
|
|
|
tag: 'INSTANCE',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function appendInitialChild(
|
|
|
|
|
parentInstance: Instance,
|
|
|
|
|
child: Instance | TextInstance,
|
|
|
|
|
): void {
|
|
|
|
|
const index = parentInstance.children.indexOf(child);
|
|
|
|
|
if (index !== -1) {
|
|
|
|
|
parentInstance.children.splice(index, 1);
|
|
|
|
|
}
|
|
|
|
|
parentInstance.children.push(child);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function finalizeInitialChildren(
|
|
|
|
|
testElement: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
props: Props,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: Object,
|
|
|
|
|
): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function prepareUpdate(
|
|
|
|
|
testElement: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
oldProps: Props,
|
|
|
|
|
newProps: Props,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: Object,
|
2020-01-09 22:50:44 +08:00
|
|
|
): null | {...} {
|
2018-05-19 18:29:11 +08:00
|
|
|
return UPDATE_SIGNAL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function shouldSetTextContent(type: string, props: Props): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function shouldDeprioritizeSubtree(type: string, props: Props): boolean {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function createTextInstance(
|
|
|
|
|
text: string,
|
|
|
|
|
rootContainerInstance: Container,
|
|
|
|
|
hostContext: Object,
|
|
|
|
|
internalInstanceHandle: Object,
|
|
|
|
|
): TextInstance {
|
2019-12-07 02:25:54 +08:00
|
|
|
if (__DEV__) {
|
2019-12-18 18:24:46 +08:00
|
|
|
if (enableDeprecatedFlareAPI) {
|
2019-12-11 11:28:14 +08:00
|
|
|
if (hostContext === EVENT_COMPONENT_CONTEXT) {
|
2019-12-15 02:09:25 +08:00
|
|
|
console.error(
|
2019-12-11 11:28:14 +08:00
|
|
|
'validateDOMNesting: React event components cannot have text DOM nodes as children. ' +
|
|
|
|
|
'Wrap the child text "%s" in an element.',
|
|
|
|
|
text,
|
|
|
|
|
);
|
|
|
|
|
}
|
2019-12-07 02:25:54 +08:00
|
|
|
}
|
2019-03-20 19:20:17 +08:00
|
|
|
}
|
2018-05-19 18:29:11 +08:00
|
|
|
return {
|
|
|
|
|
text,
|
Hide timed-out children instead of deleting them so their state is preserved (#13823)
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
|
|
|
isHidden: false,
|
2018-05-19 18:29:11 +08:00
|
|
|
tag: 'TEXT',
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-07-07 07:04:45 +08:00
|
|
|
export const isPrimaryRenderer = false;
|
2019-07-03 05:20:17 +08:00
|
|
|
export const warnsIfNotActing = true;
|
2018-05-19 18:29:11 +08:00
|
|
|
|
2018-07-04 10:22:41 +08:00
|
|
|
export const scheduleTimeout = setTimeout;
|
|
|
|
|
export const cancelTimeout = clearTimeout;
|
|
|
|
|
export const noTimeout = -1;
|
|
|
|
|
|
2018-05-19 18:29:11 +08:00
|
|
|
// -------------------
|
|
|
|
|
// Mutation
|
|
|
|
|
// -------------------
|
|
|
|
|
|
|
|
|
|
export const supportsMutation = true;
|
|
|
|
|
|
|
|
|
|
export function commitUpdate(
|
|
|
|
|
instance: Instance,
|
2020-01-09 22:50:44 +08:00
|
|
|
updatePayload: {...},
|
2018-05-19 18:29:11 +08:00
|
|
|
type: string,
|
|
|
|
|
oldProps: Props,
|
|
|
|
|
newProps: Props,
|
|
|
|
|
internalInstanceHandle: Object,
|
|
|
|
|
): void {
|
|
|
|
|
instance.type = type;
|
|
|
|
|
instance.props = newProps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function commitMount(
|
|
|
|
|
instance: Instance,
|
|
|
|
|
type: string,
|
|
|
|
|
newProps: Props,
|
|
|
|
|
internalInstanceHandle: Object,
|
|
|
|
|
): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function commitTextUpdate(
|
|
|
|
|
textInstance: TextInstance,
|
|
|
|
|
oldText: string,
|
|
|
|
|
newText: string,
|
|
|
|
|
): void {
|
|
|
|
|
textInstance.text = newText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function resetTextContent(testElement: Instance): void {
|
|
|
|
|
// noop
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const appendChildToContainer = appendChild;
|
|
|
|
|
export const insertInContainerBefore = insertBefore;
|
|
|
|
|
export const removeChildFromContainer = removeChild;
|
Hide timed-out children instead of deleting them so their state is preserved (#13823)
* Store the start time on `updateQueue` instead of `stateNode`
Originally I did this to free the `stateNode` field to store a second
set of children. I don't we'll need this anymore, since we use fragment
fibers instead. But I still think using `updateQueue` makes more sense
so I'll leave this in.
* Use fragment fibers to keep the primary and fallback children separate
If the children timeout, we switch to showing the fallback children in
place of the "primary" children. However, we don't want to delete the
primary children because then their state will be lost (both the React
state and the host state, e.g. uncontrolled form inputs). Instead we
keep them mounted and hide them. Both the fallback children AND the
primary children are rendered at the same time. Once the primary
children are un-suspended, we can delete the fallback children — don't
need to preserve their state.
The two sets of children are siblings in the host environment, but
semantically, for purposes of reconciliation, they are two separate
sets. So we store them using two fragment fibers.
However, we want to avoid allocating extra fibers for every placeholder.
They're only necessary when the children time out, because that's the
only time when both sets are mounted.
So, the extra fragment fibers are only used if the children time out.
Otherwise, we render the primary children directly. This requires some
custom reconciliation logic to preserve the state of the primary
children. It's essentially a very basic form of re-parenting.
* Use `memoizedState` to store various pieces of SuspenseComponent's state
SuspenseComponent has three pieces of state:
- alreadyCaptured: Whether a component in the child subtree already
suspended. If true, subsequent suspends should bubble up to the
next boundary.
- didTimeout: Whether the boundary renders the primary or fallback
children. This is separate from `alreadyCaptured` because outside of
strict mode, when a boundary times out, the first commit renders the
primary children in an incomplete state, then performs a second commit
to switch the fallback. In that first commit, `alreadyCaptured` is
false and `didTimeout` is true.
- timedOutAt: The time at which the boundary timed out. This is separate
from `didTimeout` because it's not set unless the boundary
actually commits.
These were previously spread across several fields.
This happens to make the non-strict case a bit less hacky; the logic for
that special case is now mostly localized to the UnwindWork module.
* Hide timed-out Suspense children
When a subtree takes too long to load, we swap its contents out for
a fallback to unblock the rest of the tree. Because we don't want
to lose the state of the timed out view, we shouldn't actually delete
the nodes from the tree. Instead, we'll keep them mounted and hide
them visually. When the subtree is unblocked, we un-hide it, having
preserved the existing state.
Adds additional host config methods. For mutation mode:
- hideInstance
- hideTextInstance
- unhideInstance
- unhideTextInstance
For persistent mode:
- cloneHiddenInstance
- cloneUnhiddenInstance
- createHiddenTextInstance
I've only implemented the new methods in the noop and test renderers.
I'll implement them in the other renderers in subsequent commits.
* Include `hidden` prop in noop renderer's output
This will be used in subsequent commits to test that timed-out children
are properly hidden.
Also adds getChildrenAsJSX() method as an alternative to using
getChildren(). (Ideally all our tests would use test renderer #oneday.)
* Implement hide/unhide host config methods for DOM renderer
For DOM nodes, we hide using `el.style.display = 'none'`.
Text nodes don't have style, so we hide using `text.textContent = ''`.
* Implement hide/unhide host config methods for Art renderer
* Create DOM fixture that tests state preservation of timed out content
* Account for class components that suspend outside concurrent mode
Need to distinguish mount from update. An unfortunate edge case :(
* Fork appendAllChildren between persistent and mutation mode
* Remove redundant check for existence of el.style
* Schedule placement effect on indeterminate components
In non-concurrent mode, indeterminate fibers may commit in an
inconsistent state. But when they update, we should throw out the
old fiber and start fresh. Which means the new fiber needs a
placement effect.
* Pass null instead of current everywhere in mountIndeterminateComponent
2018-10-19 06:37:16 +08:00
|
|
|
|
|
|
|
|
export function hideInstance(instance: Instance): void {
|
|
|
|
|
instance.isHidden = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function hideTextInstance(textInstance: TextInstance): void {
|
|
|
|
|
textInstance.isHidden = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function unhideInstance(instance: Instance, props: Props): void {
|
|
|
|
|
instance.isHidden = false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function unhideTextInstance(
|
|
|
|
|
textInstance: TextInstance,
|
|
|
|
|
text: string,
|
|
|
|
|
): void {
|
|
|
|
|
textInstance.isHidden = false;
|
|
|
|
|
}
|
2019-03-15 01:02:42 +08:00
|
|
|
|
2019-12-18 18:24:46 +08:00
|
|
|
export function DEPRECATED_mountResponderInstance(
|
2019-07-24 06:46:44 +08:00
|
|
|
responder: ReactEventResponder<any, any>,
|
|
|
|
|
responderInstance: ReactEventResponderInstance<any, any>,
|
|
|
|
|
props: Object,
|
|
|
|
|
state: Object,
|
|
|
|
|
instance: Instance,
|
|
|
|
|
) {
|
2019-07-24 18:31:33 +08:00
|
|
|
// noop
|
2019-04-09 19:47:32 +08:00
|
|
|
}
|
|
|
|
|
|
2019-12-18 18:24:46 +08:00
|
|
|
export function DEPRECATED_unmountResponderInstance(
|
2019-07-24 06:46:44 +08:00
|
|
|
responderInstance: ReactEventResponderInstance<any, any>,
|
2019-04-06 14:51:21 +08:00
|
|
|
): void {
|
2019-07-24 18:31:33 +08:00
|
|
|
// noop
|
2019-04-06 14:51:21 +08:00
|
|
|
}
|
2019-07-20 05:20:28 +08:00
|
|
|
|
2020-01-23 02:02:30 +08:00
|
|
|
export function getFundamentalComponentInstance(
|
|
|
|
|
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
|
|
|
|
|
): Instance {
|
2019-07-20 05:20:28 +08:00
|
|
|
const {impl, props, state} = fundamentalInstance;
|
|
|
|
|
return impl.getInstance(null, props, state);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function mountFundamentalComponent(
|
|
|
|
|
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
|
|
|
|
|
): void {
|
|
|
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
|
|
|
const onMount = impl.onMount;
|
|
|
|
|
if (onMount !== undefined) {
|
|
|
|
|
onMount(null, instance, props, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function shouldUpdateFundamentalComponent(
|
|
|
|
|
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
|
|
|
|
|
): boolean {
|
|
|
|
|
const {impl, prevProps, props, state} = fundamentalInstance;
|
|
|
|
|
const shouldUpdate = impl.shouldUpdate;
|
|
|
|
|
if (shouldUpdate !== undefined) {
|
|
|
|
|
return shouldUpdate(null, prevProps, props, state);
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function updateFundamentalComponent(
|
|
|
|
|
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
|
|
|
|
|
): void {
|
|
|
|
|
const {impl, instance, prevProps, props, state} = fundamentalInstance;
|
|
|
|
|
const onUpdate = impl.onUpdate;
|
|
|
|
|
if (onUpdate !== undefined) {
|
|
|
|
|
onUpdate(null, instance, prevProps, props, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function unmountFundamentalComponent(
|
|
|
|
|
fundamentalInstance: ReactFundamentalComponentInstance<any, any>,
|
|
|
|
|
): void {
|
|
|
|
|
const {impl, instance, props, state} = fundamentalInstance;
|
|
|
|
|
const onUnmount = impl.onUnmount;
|
|
|
|
|
if (onUnmount !== undefined) {
|
|
|
|
|
onUnmount(null, instance, props, state);
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-10-09 01:32:53 +08:00
|
|
|
|
2019-10-11 22:58:27 +08:00
|
|
|
export function getInstanceFromNode(mockNode: Object) {
|
|
|
|
|
const instance = nodeToInstanceMap.get(mockNode);
|
|
|
|
|
if (instance !== undefined) {
|
|
|
|
|
return instance.internalInstanceHandle;
|
|
|
|
|
}
|
|
|
|
|
return null;
|
2019-10-09 01:32:53 +08:00
|
|
|
}
|
2019-11-14 04:46:00 +08:00
|
|
|
|
2020-01-23 02:02:30 +08:00
|
|
|
export function beforeRemoveInstance(instance: any) {
|
2019-11-14 04:46:00 +08:00
|
|
|
// noop
|
|
|
|
|
}
|
2020-03-12 17:12:06 +08:00
|
|
|
|
2020-04-07 08:17:27 +08:00
|
|
|
let clientId: number = 0;
|
|
|
|
|
export function makeClientId(): OpaqueIDType {
|
|
|
|
|
return 'c_' + (clientId++).toString(36);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function makeClientIdInDEV(warnOnAccessInDEV: () => void): OpaqueIDType {
|
|
|
|
|
const id = 'c_' + (clientId++).toString(36);
|
|
|
|
|
return {
|
|
|
|
|
toString() {
|
|
|
|
|
warnOnAccessInDEV();
|
|
|
|
|
return id;
|
|
|
|
|
},
|
|
|
|
|
valueOf() {
|
|
|
|
|
warnOnAccessInDEV();
|
|
|
|
|
return id;
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let serverId: number = 0;
|
|
|
|
|
export function makeServerId(): OpaqueIDType {
|
|
|
|
|
return 's_' + (serverId++).toString(36);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function isOpaqueHydratingObject(value: mixed): boolean {
|
|
|
|
|
return (
|
|
|
|
|
value !== null &&
|
|
|
|
|
typeof value === 'object' &&
|
|
|
|
|
value.$$typeof === REACT_OPAQUE_ID_TYPE
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function makeOpaqueHydratingObject(
|
|
|
|
|
attemptToReadValue: () => void,
|
|
|
|
|
): OpaqueIDType {
|
|
|
|
|
return {
|
|
|
|
|
$$typeof: REACT_OPAQUE_ID_TYPE,
|
|
|
|
|
toString: attemptToReadValue,
|
|
|
|
|
valueOf: attemptToReadValue,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-17 05:46:17 +08:00
|
|
|
export function registerEvent(event: any, rootContainerInstance: Container) {
|
|
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|
|
|
|
|
|
2020-03-12 17:12:06 +08:00
|
|
|
export function mountEventListener(listener: any) {
|
|
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function unmountEventListener(listener: any) {
|
|
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function validateEventListenerTarget(target: any, listener: any) {
|
|
|
|
|
throw new Error('Not yet implemented.');
|
|
|
|
|
}
|