mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
d121c67004
<!-- Thanks for submitting a pull request! We appreciate you spending the time to work on these changes. Please provide enough information so that others can review your pull request. The three fields below are mandatory. Before submitting a pull request, please make sure the following is done: 1. Fork [the repository](https://github.com/facebook/react) and create your branch from `main`. 2. Run `yarn` in the repository root. 3. If you've fixed a bug or added code that should be tested, add tests! 4. Ensure the test suite passes (`yarn test`). Tip: `yarn test --watch TestName` is helpful in development. 5. Run `yarn test --prod` to test in the production environment. It supports the same options as `yarn test`. 6. If you need a debugger, run `yarn debug-test --watch TestName`, open `chrome://inspect`, and press "Inspect". 7. Format your code with [prettier](https://github.com/prettier/prettier) (`yarn prettier`). 8. Make sure your code lints (`yarn lint`). Tip: `yarn linc` to only check changed files. 9. Run the [Flow](https://flowtype.org/) type checks (`yarn flow`). 10. If you haven't already, complete the CLA. Learn more about contributing: https://reactjs.org/docs/how-to-contribute.html --> ## Summary Browsers restore state like forms and scroll position right after the popstate event. To make sure the page work as expected on back or forward button, we need to flush transitions scheduled in a popstate synchronously, and only yields if it suspends. This PR adds a new HostConfig method to check if `window.event === 'popstate'`, and `scheduleMicrotask` if a transition is scheduled in a `PopStateEvent`. ## How did you test this change? yarn test
490 lines
11 KiB
JavaScript
490 lines
11 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
import Transform from 'art/core/transform';
|
|
import Mode from 'art/modes/current';
|
|
|
|
import {TYPES, EVENT_TYPES, childrenAsString} from './ReactARTInternals';
|
|
|
|
import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities';
|
|
|
|
const pooledTransform = new Transform();
|
|
|
|
const NO_CONTEXT = {};
|
|
const UPDATE_SIGNAL = {};
|
|
if (__DEV__) {
|
|
Object.freeze(NO_CONTEXT);
|
|
Object.freeze(UPDATE_SIGNAL);
|
|
}
|
|
|
|
/** Helper Methods */
|
|
|
|
function addEventListeners(instance, type, listener) {
|
|
// We need to explicitly unregister before unmount.
|
|
// For this reason we need to track subscriptions.
|
|
if (!instance._listeners) {
|
|
instance._listeners = {};
|
|
instance._subscriptions = {};
|
|
}
|
|
|
|
instance._listeners[type] = listener;
|
|
|
|
if (listener) {
|
|
if (!instance._subscriptions[type]) {
|
|
instance._subscriptions[type] = instance.subscribe(
|
|
type,
|
|
createEventHandler(instance),
|
|
instance,
|
|
);
|
|
}
|
|
} else {
|
|
if (instance._subscriptions[type]) {
|
|
instance._subscriptions[type]();
|
|
delete instance._subscriptions[type];
|
|
}
|
|
}
|
|
}
|
|
|
|
function createEventHandler(instance) {
|
|
return function handleEvent(event) {
|
|
const listener = instance._listeners[event.type];
|
|
|
|
if (!listener) {
|
|
// Noop
|
|
} else if (typeof listener === 'function') {
|
|
listener.call(instance, event);
|
|
} else if (listener.handleEvent) {
|
|
listener.handleEvent(event);
|
|
}
|
|
};
|
|
}
|
|
|
|
function destroyEventListeners(instance) {
|
|
if (instance._subscriptions) {
|
|
for (const type in instance._subscriptions) {
|
|
instance._subscriptions[type]();
|
|
}
|
|
}
|
|
|
|
instance._subscriptions = null;
|
|
instance._listeners = null;
|
|
}
|
|
|
|
function getScaleX(props) {
|
|
if (props.scaleX != null) {
|
|
return props.scaleX;
|
|
} else if (props.scale != null) {
|
|
return props.scale;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
function getScaleY(props) {
|
|
if (props.scaleY != null) {
|
|
return props.scaleY;
|
|
} else if (props.scale != null) {
|
|
return props.scale;
|
|
} else {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
function isSameFont(oldFont, newFont) {
|
|
if (oldFont === newFont) {
|
|
return true;
|
|
} else if (typeof newFont === 'string' || typeof oldFont === 'string') {
|
|
return false;
|
|
} else {
|
|
return (
|
|
newFont.fontSize === oldFont.fontSize &&
|
|
newFont.fontStyle === oldFont.fontStyle &&
|
|
newFont.fontVariant === oldFont.fontVariant &&
|
|
newFont.fontWeight === oldFont.fontWeight &&
|
|
newFont.fontFamily === oldFont.fontFamily
|
|
);
|
|
}
|
|
}
|
|
|
|
/** Render Methods */
|
|
|
|
function applyClippingRectangleProps(instance, props, prevProps = {}) {
|
|
applyNodeProps(instance, props, prevProps);
|
|
|
|
instance.width = props.width;
|
|
instance.height = props.height;
|
|
}
|
|
|
|
function applyGroupProps(instance, props, prevProps = {}) {
|
|
applyNodeProps(instance, props, prevProps);
|
|
|
|
instance.width = props.width;
|
|
instance.height = props.height;
|
|
}
|
|
|
|
function applyNodeProps(instance, props, prevProps = {}) {
|
|
const scaleX = getScaleX(props);
|
|
const scaleY = getScaleY(props);
|
|
|
|
pooledTransform
|
|
.transformTo(1, 0, 0, 1, 0, 0)
|
|
.move(props.x || 0, props.y || 0)
|
|
.rotate(props.rotation || 0, props.originX, props.originY)
|
|
.scale(scaleX, scaleY, props.originX, props.originY);
|
|
|
|
if (props.transform != null) {
|
|
pooledTransform.transform(props.transform);
|
|
}
|
|
|
|
if (
|
|
instance.xx !== pooledTransform.xx ||
|
|
instance.yx !== pooledTransform.yx ||
|
|
instance.xy !== pooledTransform.xy ||
|
|
instance.yy !== pooledTransform.yy ||
|
|
instance.x !== pooledTransform.x ||
|
|
instance.y !== pooledTransform.y
|
|
) {
|
|
instance.transformTo(pooledTransform);
|
|
}
|
|
|
|
if (props.cursor !== prevProps.cursor || props.title !== prevProps.title) {
|
|
instance.indicate(props.cursor, props.title);
|
|
}
|
|
|
|
if (instance.blend && props.opacity !== prevProps.opacity) {
|
|
instance.blend(props.opacity == null ? 1 : props.opacity);
|
|
}
|
|
|
|
if (props.visible !== prevProps.visible) {
|
|
if (props.visible == null || props.visible) {
|
|
instance.show();
|
|
} else {
|
|
instance.hide();
|
|
}
|
|
}
|
|
|
|
for (const type in EVENT_TYPES) {
|
|
addEventListeners(instance, EVENT_TYPES[type], props[type]);
|
|
}
|
|
}
|
|
|
|
function applyRenderableNodeProps(instance, props, prevProps = {}) {
|
|
applyNodeProps(instance, props, prevProps);
|
|
|
|
if (prevProps.fill !== props.fill) {
|
|
if (props.fill && props.fill.applyFill) {
|
|
props.fill.applyFill(instance);
|
|
} else {
|
|
instance.fill(props.fill);
|
|
}
|
|
}
|
|
if (
|
|
prevProps.stroke !== props.stroke ||
|
|
prevProps.strokeWidth !== props.strokeWidth ||
|
|
prevProps.strokeCap !== props.strokeCap ||
|
|
prevProps.strokeJoin !== props.strokeJoin ||
|
|
// TODO: Consider deep check of stokeDash; may benefit VML in IE.
|
|
prevProps.strokeDash !== props.strokeDash
|
|
) {
|
|
instance.stroke(
|
|
props.stroke,
|
|
props.strokeWidth,
|
|
props.strokeCap,
|
|
props.strokeJoin,
|
|
props.strokeDash,
|
|
);
|
|
}
|
|
}
|
|
|
|
function applyShapeProps(instance, props, prevProps = {}) {
|
|
applyRenderableNodeProps(instance, props, prevProps);
|
|
|
|
const path = props.d || childrenAsString(props.children);
|
|
|
|
const prevDelta = instance._prevDelta;
|
|
const prevPath = instance._prevPath;
|
|
|
|
if (
|
|
path !== prevPath ||
|
|
path.delta !== prevDelta ||
|
|
prevProps.height !== props.height ||
|
|
prevProps.width !== props.width
|
|
) {
|
|
instance.draw(path, props.width, props.height);
|
|
|
|
instance._prevDelta = path.delta;
|
|
instance._prevPath = path;
|
|
}
|
|
}
|
|
|
|
function applyTextProps(instance, props, prevProps = {}) {
|
|
applyRenderableNodeProps(instance, props, prevProps);
|
|
|
|
const string = props.children;
|
|
|
|
if (
|
|
instance._currentString !== string ||
|
|
!isSameFont(props.font, prevProps.font) ||
|
|
props.alignment !== prevProps.alignment ||
|
|
props.path !== prevProps.path
|
|
) {
|
|
instance.draw(string, props.font, props.alignment, props.path);
|
|
|
|
instance._currentString = string;
|
|
}
|
|
}
|
|
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoHydration';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoScopes';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoTestSelectors';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoMicrotasks';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoResources';
|
|
export * from 'react-reconciler/src/ReactFiberConfigWithNoSingletons';
|
|
|
|
export function appendInitialChild(parentInstance, child) {
|
|
if (typeof child === 'string') {
|
|
// Noop for string children of Text (eg <Text>{'foo'}{'bar'}</Text>)
|
|
throw new Error('Text children should already be flattened.');
|
|
}
|
|
|
|
child.inject(parentInstance);
|
|
}
|
|
|
|
export function createInstance(type, props, internalInstanceHandle) {
|
|
let instance;
|
|
|
|
switch (type) {
|
|
case TYPES.CLIPPING_RECTANGLE:
|
|
instance = Mode.ClippingRectangle();
|
|
instance._applyProps = applyClippingRectangleProps;
|
|
break;
|
|
case TYPES.GROUP:
|
|
instance = Mode.Group();
|
|
instance._applyProps = applyGroupProps;
|
|
break;
|
|
case TYPES.SHAPE:
|
|
instance = Mode.Shape();
|
|
instance._applyProps = applyShapeProps;
|
|
break;
|
|
case TYPES.TEXT:
|
|
instance = Mode.Text(
|
|
props.children,
|
|
props.font,
|
|
props.alignment,
|
|
props.path,
|
|
);
|
|
instance._applyProps = applyTextProps;
|
|
break;
|
|
}
|
|
|
|
if (!instance) {
|
|
throw new Error(`ReactART does not support the type "${type}"`);
|
|
}
|
|
|
|
instance._applyProps(instance, props);
|
|
|
|
return instance;
|
|
}
|
|
|
|
export function createTextInstance(
|
|
text,
|
|
rootContainerInstance,
|
|
internalInstanceHandle,
|
|
) {
|
|
return text;
|
|
}
|
|
|
|
export function finalizeInitialChildren(domElement, type, props) {
|
|
return false;
|
|
}
|
|
|
|
export function getPublicInstance(instance) {
|
|
return instance;
|
|
}
|
|
|
|
export function prepareForCommit() {
|
|
// Noop
|
|
return null;
|
|
}
|
|
|
|
export function prepareUpdate(domElement, type, oldProps, newProps) {
|
|
return UPDATE_SIGNAL;
|
|
}
|
|
|
|
export function resetAfterCommit() {
|
|
// Noop
|
|
}
|
|
|
|
export function resetTextContent(domElement) {
|
|
// Noop
|
|
}
|
|
|
|
export function getRootHostContext() {
|
|
return NO_CONTEXT;
|
|
}
|
|
|
|
export function getChildHostContext() {
|
|
return NO_CONTEXT;
|
|
}
|
|
|
|
export const scheduleTimeout = setTimeout;
|
|
export const cancelTimeout = clearTimeout;
|
|
export const noTimeout = -1;
|
|
|
|
export function shouldSetTextContent(type, props) {
|
|
return (
|
|
typeof props.children === 'string' || typeof props.children === 'number'
|
|
);
|
|
}
|
|
|
|
export function getCurrentEventPriority() {
|
|
return DefaultEventPriority;
|
|
}
|
|
|
|
export function shouldAttemptEagerTransition() {
|
|
return false;
|
|
}
|
|
|
|
// The ART renderer is secondary to the React DOM renderer.
|
|
export const isPrimaryRenderer = false;
|
|
|
|
// The ART renderer shouldn't trigger missing act() warnings
|
|
export const warnsIfNotActing = false;
|
|
|
|
export const supportsMutation = true;
|
|
|
|
export function appendChild(parentInstance, child) {
|
|
if (child.parentNode === parentInstance) {
|
|
child.eject();
|
|
}
|
|
child.inject(parentInstance);
|
|
}
|
|
|
|
export function appendChildToContainer(parentInstance, child) {
|
|
if (child.parentNode === parentInstance) {
|
|
child.eject();
|
|
}
|
|
child.inject(parentInstance);
|
|
}
|
|
|
|
export function insertBefore(parentInstance, child, beforeChild) {
|
|
if (child === beforeChild) {
|
|
throw new Error('ReactART: Can not insert node before itself');
|
|
}
|
|
|
|
child.injectBefore(beforeChild);
|
|
}
|
|
|
|
export function insertInContainerBefore(parentInstance, child, beforeChild) {
|
|
if (child === beforeChild) {
|
|
throw new Error('ReactART: Can not insert node before itself');
|
|
}
|
|
|
|
child.injectBefore(beforeChild);
|
|
}
|
|
|
|
export function removeChild(parentInstance, child) {
|
|
destroyEventListeners(child);
|
|
child.eject();
|
|
}
|
|
|
|
export function removeChildFromContainer(parentInstance, child) {
|
|
destroyEventListeners(child);
|
|
child.eject();
|
|
}
|
|
|
|
export function commitTextUpdate(textInstance, oldText, newText) {
|
|
// Noop
|
|
}
|
|
|
|
export function commitMount(instance, type, newProps) {
|
|
// Noop
|
|
}
|
|
|
|
export function commitUpdate(
|
|
instance,
|
|
updatePayload,
|
|
type,
|
|
oldProps,
|
|
newProps,
|
|
) {
|
|
instance._applyProps(instance, newProps, oldProps);
|
|
}
|
|
|
|
export function hideInstance(instance) {
|
|
instance.hide();
|
|
}
|
|
|
|
export function hideTextInstance(textInstance) {
|
|
// Noop
|
|
}
|
|
|
|
export function unhideInstance(instance, props) {
|
|
if (props.visible == null || props.visible) {
|
|
instance.show();
|
|
}
|
|
}
|
|
|
|
export function unhideTextInstance(textInstance, text): void {
|
|
// Noop
|
|
}
|
|
|
|
export function clearContainer(container) {
|
|
// TODO Implement this
|
|
}
|
|
|
|
export function getInstanceFromNode(node) {
|
|
throw new Error('Not implemented.');
|
|
}
|
|
|
|
export function beforeActiveInstanceBlur(internalInstanceHandle: Object) {
|
|
// noop
|
|
}
|
|
|
|
export function afterActiveInstanceBlur() {
|
|
// noop
|
|
}
|
|
|
|
export function preparePortalMount(portalInstance: any): void {
|
|
// noop
|
|
}
|
|
|
|
// eslint-disable-next-line no-undef
|
|
export function detachDeletedInstance(node: Instance): void {
|
|
// noop
|
|
}
|
|
|
|
export function requestPostPaintCallback(callback: (time: number) => void) {
|
|
// noop
|
|
}
|
|
|
|
export function maySuspendCommit(type, props) {
|
|
return false;
|
|
}
|
|
|
|
export function preloadInstance(type, props) {
|
|
// Return true to indicate it's already loaded
|
|
return true;
|
|
}
|
|
|
|
export function startSuspendingCommit() {}
|
|
|
|
export function suspendInstance(type, props) {}
|
|
|
|
export function waitForCommitToBeReady() {
|
|
return null;
|
|
}
|
|
// eslint-disable-next-line no-undef
|
|
export function prepareRendererToRender(container: Container): void {
|
|
// noop
|
|
}
|
|
|
|
export function resetRendererAfterRender(): void {
|
|
// noop
|
|
}
|