1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 *7 * @flow8 */910import type {DOMEventName} from '../events/DOMEventNames';11import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';12import type {13 BoundingRect,14 IntersectionObserverOptions,15 ObserveVisibleRectsCallback,16} from 'react-reconciler/src/ReactTestSelectors';17import type {ReactContext, ReactScopeInstance} from 'shared/ReactTypes';18import type {AncestorInfoDev} from './validateDOMNesting';19import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';20import type {21 CrossOriginEnum,22 PreloadImplOptions,23 PreloadModuleImplOptions,24 PreinitStyleOptions,25 PreinitScriptOptions,26 PreinitModuleScriptOptions,27} from 'react-dom/src/shared/ReactDOMTypes';28import type {TransitionTypes} from 'react/src/ReactTransitionType';2930import {NotPending} from '../shared/ReactDOMFormActions';3132import {setSrcObject} from './ReactDOMSrcObject';3334import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext';35import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber';3637import hasOwnProperty from 'shared/hasOwnProperty';38import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';39import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols';4041export {42 setCurrentUpdatePriority,43 getCurrentUpdatePriority,44 resolveUpdatePriority,45} from './ReactDOMUpdatePriority';46import {47 precacheFiberNode,48 updateFiberProps,49 getFiberCurrentPropsFromNode,50 getInstanceFromNode,51 getClosestInstanceFromNode,52 getFiberFromScopeInstance,53 getInstanceFromNode as getInstanceFromNodeDOMTree,54 isContainerMarkedAsRoot,55 detachDeletedInstance,56 getResourcesFromRoot,57 isMarkedHoistable,58 markNodeAsHoistable,59 markNodeAsPendingLoad,60 clearPendingLoadOnNode,61 isNodePendingLoad,62 isOwnedInstance,63} from './ReactDOMComponentTree';64import {65 traverseFragmentInstancesAndTextInstances,66 getFragmentParentInstanceOrContainerFiber,67 getInstanceFromHostFiber,68 isFiberFollowing,69 isFiberPreceding,70 getFragmentInstanceOrTextInstanceSiblings,71 traverseFragmentInstancesAndTextInstancesDeeply,72 fiberIsPortaledIntoHost,73 getFragmentPortalContainerInfo,74 getFragmentRootContainerInfo,75 isFiberContainedByFragment,76 isFragmentContainedByFiber,77} from 'react-reconciler/src/ReactFiberTreeReflection';78import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';7980export {detachDeletedInstance};81import {hasRole} from './DOMAccessibilityRoles';82import type {SingletonType} from './ReactDOMComponent';83import {84 setInitialProperties,85 updateProperties,86 clearSingletonProperties,87 hydrateProperties,88 hydrateText,89 diffHydratedProperties,90 getPropsFromElement,91 diffHydratedText,92 trapClickOnNonInteractiveElement,93 clearClickListener,94} from './ReactDOMComponent';95import {hydrateInput} from './ReactDOMInput';96import {hydrateTextarea} from './ReactDOMTextarea';97import {hydrateSelect} from './ReactDOMSelect';98import {getSelectionInformation, restoreSelection} from './ReactInputSelection';99import setTextContent from './setTextContent';100import {101 validateDOMNesting,102 validateTextNesting,103 updatedAncestorInfoDev,104} from './validateDOMNesting';105import {106 isEnabled as ReactBrowserEventEmitterIsEnabled,107 setEnabled as ReactBrowserEventEmitterSetEnabled,108} from '../events/ReactDOMEventListener';109import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';110import {111 ELEMENT_NODE,112 TEXT_NODE,113 COMMENT_NODE,114 DOCUMENT_NODE,115 DOCUMENT_TYPE_NODE,116 DOCUMENT_FRAGMENT_NODE,117} from './HTMLNodeType';118119import {120 flushEventReplaying,121 retryIfBlockedOn,122} from '../events/ReactDOMEventReplaying';123124import {125 enableCreateEventHandleAPI,126 enableScopeAPI,127 disableLegacyMode,128 enableMoveBefore,129 disableCommentsAsDOMContainers,130 enableSuspenseyImages,131 enableSrcObject,132 enableViewTransition,133 enableHydrationChangeEvent,134 enableProfilerTimer,135} from 'shared/ReactFeatureFlags';136import {137 HostComponent,138 HostHoistable,139 HostText,140 HostSingleton,141} from 'react-reconciler/src/ReactWorkTags';142import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';143import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';144import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';145import {flushSyncWork as flushSyncWorkOnAllRoots} from 'react-reconciler/src/ReactFiberWorkLoop';146import {requestFormReset as requestFormResetOnFiber} from 'react-reconciler/src/ReactFiberHooks';147148import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';149150export {default as rendererVersion} from 'shared/ReactVersion';151152import noop from 'shared/noop';153import estimateBandwidth from './estimateBandwidth';154155export const rendererPackageName = 'react-dom';156export const extraDevToolsConfig = null;157158export type Type = string;159export type Props = {160 autoFocus?: boolean,161 children?: mixed,162 disabled?: boolean,163 hidden?: boolean,164 suppressHydrationWarning?: boolean,165 dangerouslySetInnerHTML?: mixed,166 style?: {167 display?: string,168 viewTransitionName?: string,169 'view-transition-name'?: string,170 viewTransitionClass?: string,171 'view-transition-class'?: string,172 margin?: string,173 marginTop?: string,174 'margin-top'?: string,175 marginBottom?: string,176 'margin-bottom'?: string,177 ...178 },179 bottom?: null | number,180 left?: null | number,181 right?: null | number,182 top?: null | number,183 is?: string,184 size?: number,185 value?: string,186 defaultValue?: string,187 checked?: boolean,188 defaultChecked?: boolean,189 multiple?: boolean,190 type?: string,191 src?: string | Blob | MediaSource | MediaStream, // TODO: Response192 srcSet?: string,193 loading?: 'eager' | 'lazy',194 onLoad?: (event: any) => void,195 ...196};197type RawProps = {198 [string]: mixed,199};200export type EventTargetChildElement = {201 type: string,202 props: null | {203 style?: {204 position?: string,205 zIndex?: number,206 bottom?: string,207 left?: string,208 right?: string,209 top?: string,210 ...211 },212 ...213 },214 ...215};216217export type Container =218 | interface extends Element {_reactRootContainer?: FiberRoot}219 | interface extends Document {_reactRootContainer?: FiberRoot}220 | interface extends DocumentFragment {_reactRootContainer?: FiberRoot};221export type Instance = Element;222export type TextInstance = Text;223224type InstanceWithFragmentHandles = Instance & {225 reactFragments?: Set<FragmentInstanceType>,226};227type HostNodeWithFragmentHandles = (Instance | TextInstance) & {228 reactFragments?: Set<FragmentInstanceType>,229};230231declare class ActivityInterface extends Comment {}232declare class SuspenseInterface extends Comment {233 _reactRetry: void | (() => void);234}235236export type ActivityInstance = ActivityInterface;237export type SuspenseInstance = SuspenseInterface;238239type FormStateMarkerInstance = Comment;240export type HydratableInstance =241 | Instance242 | TextInstance243 | ActivityInstance244 | SuspenseInstance245 | FormStateMarkerInstance;246export type PublicInstance = Element | Text;247export type HostContextDev = {248 context: HostContextProd,249 ancestorInfo: AncestorInfoDev,250};251type HostContextProd = HostContextNamespace;252export type HostContext = HostContextDev | HostContextProd;253export type UpdatePayload = Array<mixed>;254export type ChildSet = void; // Unused255export type TimeoutHandle = TimeoutID;256export type NoTimeout = -1;257export type RendererInspectionConfig = $ReadOnly<{}>;258259export type TransitionStatus = FormStatus;260261export type ViewTransitionInstance = {262 name: string,263 group: mixin$Animatable,264 imagePair: mixin$Animatable,265 old: mixin$Animatable,266 new: mixin$Animatable,267};268269type SelectionInformation = {270 focusedElem: null | HTMLElement,271 selectionRange: mixed,272};273274const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';275276const ACTIVITY_START_DATA = '&';277const ACTIVITY_END_DATA = '/&';278const SUSPENSE_START_DATA = '$';279const SUSPENSE_END_DATA = '/$';280const SUSPENSE_PENDING_START_DATA = '$?';281const SUSPENSE_QUEUED_START_DATA = '$~';282const SUSPENSE_FALLBACK_START_DATA = '$!';283const PREAMBLE_CONTRIBUTION_HTML = 'html';284const PREAMBLE_CONTRIBUTION_BODY = 'body';285const PREAMBLE_CONTRIBUTION_HEAD = 'head';286const FORM_STATE_IS_MATCHING = 'F!';287const FORM_STATE_IS_NOT_MATCHING = 'F';288289const DOCUMENT_READY_STATE_LOADING = 'loading';290291const STYLE = 'style';292293opaque type HostContextNamespace = 0 | 1 | 2;294export const HostContextNamespaceNone: HostContextNamespace = 0;295const HostContextNamespaceSvg: HostContextNamespace = 1;296const HostContextNamespaceMath: HostContextNamespace = 2;297298let eventsEnabled: ?boolean = null;299let selectionInformation: null | SelectionInformation = null;300301export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';302303function getOwnerDocumentFromRootContainer(304 rootContainerElement: Element | Document | DocumentFragment,305): Document {306 return rootContainerElement.nodeType === DOCUMENT_NODE307 ? (rootContainerElement as any)308 : rootContainerElement.ownerDocument;309}310311export function getRootHostContext(312 rootContainerInstance: Container,313): HostContext {314 let type;315 let context: HostContextProd;316 const nodeType = rootContainerInstance.nodeType;317 switch (nodeType) {318 case DOCUMENT_NODE:319 case DOCUMENT_FRAGMENT_NODE: {320 type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';321 const root = (rootContainerInstance as any).documentElement;322 if (root) {323 const namespaceURI = root.namespaceURI;324 context = namespaceURI325 ? getOwnHostContext(namespaceURI)326 : HostContextNamespaceNone;327 } else {328 context = HostContextNamespaceNone;329 }330 break;331 }332 default: {333 const container: any =334 !disableCommentsAsDOMContainers && nodeType === COMMENT_NODE335 ? rootContainerInstance.parentNode336 : rootContainerInstance;337 type = container.tagName;338 const namespaceURI = container.namespaceURI;339 if (!namespaceURI) {340 switch (type) {341 case 'svg':342 context = HostContextNamespaceSvg;343 break;344 case 'math':345 context = HostContextNamespaceMath;346 break;347 default:348 context = HostContextNamespaceNone;349 break;350 }351 } else {352 const ownContext = getOwnHostContext(namespaceURI);353 context = getChildHostContextProd(ownContext, type);354 }355 break;356 }357 }358 if (__DEV__) {359 const validatedTag = type.toLowerCase();360 const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);361 return {context, ancestorInfo};362 }363 return context;364}365366function getOwnHostContext(namespaceURI: string): HostContextNamespace {367 switch (namespaceURI) {368 case SVG_NAMESPACE:369 return HostContextNamespaceSvg;370 case MATH_NAMESPACE:371 return HostContextNamespaceMath;372 default:373 return HostContextNamespaceNone;374 }375}376377function getChildHostContextProd(378 parentNamespace: HostContextNamespace,379 type: string,380): HostContextNamespace {381 if (parentNamespace === HostContextNamespaceNone) {382 // No (or default) parent namespace: potential entry point.383 switch (type) {384 case 'svg':385 return HostContextNamespaceSvg;386 case 'math':387 return HostContextNamespaceMath;388 default:389 return HostContextNamespaceNone;390 }391 }392 if (parentNamespace === HostContextNamespaceSvg && type === 'foreignObject') {393 // We're leaving SVG.394 return HostContextNamespaceNone;395 }396 // By default, pass namespace below.397 return parentNamespace;398}399400export function getChildHostContext(401 parentHostContext: HostContext,402 type: string,403): HostContext {404 if (__DEV__) {405 const parentHostContextDev = parentHostContext as any as HostContextDev;406 const context = getChildHostContextProd(parentHostContextDev.context, type);407 const ancestorInfo = updatedAncestorInfoDev(408 parentHostContextDev.ancestorInfo,409 type,410 );411 return {context, ancestorInfo};412 }413 const parentNamespace = parentHostContext as any as HostContextProd;414 return getChildHostContextProd(parentNamespace, type);415}416417export function getPublicInstance(instance: Instance): Instance {418 return instance;419}420421export function prepareForCommit(containerInfo: Container): Object | null {422 eventsEnabled = ReactBrowserEventEmitterIsEnabled();423 selectionInformation = getSelectionInformation(containerInfo);424 let activeInstance = null;425 if (enableCreateEventHandleAPI) {426 const focusedElem = selectionInformation.focusedElem;427 if (focusedElem !== null) {428 activeInstance = getClosestInstanceFromNode(focusedElem);429 }430 }431 ReactBrowserEventEmitterSetEnabled(false);432 return activeInstance;433}434435export function beforeActiveInstanceBlur(internalInstanceHandle: Object): void {436 if (enableCreateEventHandleAPI) {437 ReactBrowserEventEmitterSetEnabled(true);438 dispatchBeforeDetachedBlur(439 (selectionInformation as any).focusedElem,440 internalInstanceHandle,441 );442 ReactBrowserEventEmitterSetEnabled(false);443 }444}445446export function afterActiveInstanceBlur(): void {447 if (enableCreateEventHandleAPI) {448 ReactBrowserEventEmitterSetEnabled(true);449 dispatchAfterDetachedBlur((selectionInformation as any).focusedElem);450 ReactBrowserEventEmitterSetEnabled(false);451 }452}453454export function resetAfterCommit(containerInfo: Container): void {455 restoreSelection(selectionInformation, containerInfo);456 ReactBrowserEventEmitterSetEnabled(eventsEnabled);457 eventsEnabled = null;458 selectionInformation = null;459}460461export function createHoistableInstance(462 type: string,463 props: Props,464 rootContainerInstance: Container,465 internalInstanceHandle: Object,466): Instance {467 const ownerDocument = getOwnerDocumentFromRootContainer(468 rootContainerInstance,469 );470471 const domElement: Instance = ownerDocument.createElement(type);472 precacheFiberNode(internalInstanceHandle, domElement);473 updateFiberProps(domElement, props);474 setInitialProperties(domElement, type, props);475 markNodeAsHoistable(domElement);476 return domElement;477}478479let didWarnScriptTags = false;480function isScriptDataBlock(props: Props): boolean {481 const scriptType = props.type;482 if (typeof scriptType !== 'string' || scriptType === '') {483 return false;484 }485 const lower = scriptType.toLowerCase();486 // Special non-MIME keywords recognized by the HTML spec487 // TODO: May be fine to also not warn about having these types be parsed as "parser-inserted"488 if (489 lower === 'module' ||490 lower === 'importmap' ||491 lower === 'speculationrules'492 ) {493 return false;494 }495 // JavaScript MIME types per https://mimesniff.spec.whatwg.org/#javascript-mime-type496 switch (lower) {497 case 'application/ecmascript':498 case 'application/javascript':499 case 'application/x-ecmascript':500 case 'application/x-javascript':501 case 'text/ecmascript':502 case 'text/javascript':503 case 'text/javascript1.0':504 case 'text/javascript1.1':505 case 'text/javascript1.2':506 case 'text/javascript1.3':507 case 'text/javascript1.4':508 case 'text/javascript1.5':509 case 'text/jscript':510 case 'text/livescript':511 case 'text/x-ecmascript':512 case 'text/x-javascript':513 return false;514 }515 // Any other non-empty type value means this is a data block516 return true;517}518const warnedUnknownTags: {519 [key: string]: boolean,520} = {521 // There are working polyfills for <dialog>. Let people use it.522 dialog: true,523 // Electron ships a custom <webview> tag to display external web content in524 // an isolated frame and process.525 // This tag is not present in non Electron environments such as JSDom which526 // is often used for testing purposes.527 // @see https://electronjs.org/docs/api/webview-tag528 webview: true,529};530531export function createInstance(532 type: string,533 props: Props,534 rootContainerInstance: Container,535 hostContext: HostContext,536 internalInstanceHandle: Object,537): Instance {538 let hostContextProd: HostContextProd;539 if (__DEV__) {540 // TODO: take namespace into account when validating.541 const hostContextDev: HostContextDev = hostContext as any;542 validateDOMNesting(type, hostContextDev.ancestorInfo);543 hostContextProd = hostContextDev.context;544 } else {545 hostContextProd = hostContext as any;546 }547548 const ownerDocument = getOwnerDocumentFromRootContainer(549 rootContainerInstance,550 );551552 let domElement: Instance;553 switch (hostContextProd) {554 case HostContextNamespaceSvg:555 domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);556 break;557 case HostContextNamespaceMath:558 domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);559 break;560 default:561 switch (type) {562 case 'svg': {563 domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);564 break;565 }566 case 'math': {567 domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);568 break;569 }570 case 'script': {571 // Create the script via .innerHTML so its "parser-inserted" flag is572 // set to true and it does not execute573 const div = ownerDocument.createElement('div');574 if (__DEV__) {575 if (576 !didWarnScriptTags &&577 // Data block scripts are not executed by UAs anyway so578 // we don't need to warn: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type579 !isScriptDataBlock(props)580 ) {581 console.error(582 'Encountered a script tag while rendering React component. ' +583 'Scripts inside React components are never executed when rendering ' +584 'on the client. Consider using template tag instead ' +585 '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).',586 );587 didWarnScriptTags = true;588 }589 }590 div.innerHTML = '<script><' + '/script>';591 // This is guaranteed to yield a script element.592 const firstChild = div.firstChild as any as HTMLScriptElement;593 domElement = div.removeChild(firstChild);594 break;595 }596 case 'select': {597 if (typeof props.is === 'string') {598 domElement = ownerDocument.createElement('select', {is: props.is});599 } else {600 // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.601 // See discussion in https://github.com/react/react/pull/6896602 // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240603 domElement = ownerDocument.createElement('select');604 }605 if (props.multiple) {606 domElement.multiple = true;607 } else if (props.size) {608 // Setting a size greater than 1 causes a select to behave like `multiple=true`, where609 // it is possible that no option is selected.610 //611 // This is only necessary when a select in "single selection mode".612 domElement.size = props.size;613 }614 break;615 }616 default: {617 if (typeof props.is === 'string') {618 domElement = ownerDocument.createElement(type, {is: props.is});619 } else {620 // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.621 // See discussion in https://github.com/react/react/pull/6896622 // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240623 domElement = ownerDocument.createElement(type);624 }625626 if (__DEV__) {627 if (type.indexOf('-') === -1) {628 // We're not SVG/MathML and we don't have a dash, so we're not a custom element629 // Even if you use `is`, these should be of known type and lower case.630 if (type !== type.toLowerCase()) {631 console.error(632 '<%s /> is using incorrect casing. ' +633 'Use PascalCase for React components, ' +634 'or lowercase for HTML elements.',635 type,636 );637 }638 if (639 // $FlowFixMe[method-unbinding]640 Object.prototype.toString.call(domElement) ===641 '[object HTMLUnknownElement]' &&642 !hasOwnProperty.call(warnedUnknownTags, type)643 ) {644 warnedUnknownTags[type] = true;645 console.error(646 'The tag <%s> is unrecognized in this browser. ' +647 'If you meant to render a React component, start its name with ' +648 'an uppercase letter.',649 type,650 );651 }652 }653 }654 }655 }656 }657 precacheFiberNode(internalInstanceHandle, domElement);658 updateFiberProps(domElement, props);659 return domElement;660}661662let didWarnForClone = false;663664export function cloneMutableInstance(665 instance: Instance,666 keepChildren: boolean,667): Instance {668 if (__DEV__) {669 // Warn for problematic670 const tagName = instance.tagName;671 switch (tagName) {672 case 'VIDEO':673 case 'IFRAME':674 if (!didWarnForClone) {675 didWarnForClone = true;676 // TODO: Once we have the ability to avoid cloning the root, suggest an absolutely677 // positioned ViewTransition instead as the solution.678 console.warn(679 'startGestureTransition() required cloning a <%s> element since it exists in ' +680 'both states of the gesture. This can be problematic since it will load it twice ' +681 'Try removing or hiding it with <Activity mode="offscreen"> in the optimistic state.',682 tagName.toLowerCase(),683 );684 }685 break;686 }687 }688 return instance.cloneNode(keepChildren);689}690691export function appendInitialChild(692 parentInstance: Instance,693 child: Instance | TextInstance,694): void {695 // Note: This should not use moveBefore() because initial are appended while disconnected.696 parentInstance.appendChild(child);697}698699export function finalizeInitialChildren(700 domElement: Instance,701 type: string,702 props: Props,703 hostContext: HostContext,704): boolean {705 setInitialProperties(domElement, type, props);706 switch (type) {707 case 'button':708 case 'input':709 case 'select':710 case 'textarea':711 return !!props.autoFocus;712 case 'img':713 return true;714 default:715 return false;716 }717}718719export function finalizeHydratedChildren(720 domElement: Instance,721 type: string,722 props: Props,723 hostContext: HostContext,724): boolean {725 // TOOD: Consider unifying this with hydrateInstance.726 if (!enableHydrationChangeEvent) {727 return false;728 }729 switch (type) {730 case 'input':731 case 'select':732 case 'textarea':733 case 'img':734 return true;735 default:736 return false;737 }738}739740export function shouldSetTextContent(type: string, props: Props): boolean {741 return (742 type === 'textarea' ||743 type === 'noscript' ||744 typeof props.children === 'string' ||745 typeof props.children === 'number' ||746 typeof props.children === 'bigint' ||747 (typeof props.dangerouslySetInnerHTML === 'object' &&748 props.dangerouslySetInnerHTML !== null &&749 props.dangerouslySetInnerHTML.__html != null)750 );751}752753export function createTextInstance(754 text: string,755 rootContainerInstance: Container,756 hostContext: HostContext,757 internalInstanceHandle: Object,758): TextInstance {759 if (__DEV__) {760 const hostContextDev = hostContext as any as HostContextDev;761 const ancestor = hostContextDev.ancestorInfo.current;762 if (ancestor != null) {763 validateTextNesting(764 text,765 ancestor.tag,766 hostContextDev.ancestorInfo.implicitRootScope,767 );768 }769 }770 const textNode: TextInstance = getOwnerDocumentFromRootContainer(771 rootContainerInstance,772 ).createTextNode(text);773 precacheFiberNode(internalInstanceHandle, textNode);774 return textNode;775}776777export function cloneMutableTextInstance(778 textInstance: TextInstance,779): TextInstance {780 return textInstance.cloneNode(false);781}782783let currentPopstateTransitionEvent: Event | null = null;784export function shouldAttemptEagerTransition(): boolean {785 const event = window.event;786 if (event && event.type === 'popstate') {787 // This is a popstate event. Attempt to render any transition during this788 // event synchronously. Unless we already attempted during this event.789 if (event === currentPopstateTransitionEvent) {790 // We already attempted to render this popstate transition synchronously.791 // Any subsequent attempts must have happened as the result of a derived792 // update, like startTransition inside useEffect, or useDV. Switch back to793 // the default behavior for all remaining transitions during the current794 // popstate event.795 return false;796 } else {797 // Cache the current event in case a derived transition is scheduled.798 // (Refer to previous branch.)799 currentPopstateTransitionEvent = event;800 return true;801 }802 }803 // We're not inside a popstate event.804 currentPopstateTransitionEvent = null;805 return false;806}807808let schedulerEvent: void | Event = undefined;809export function trackSchedulerEvent(): void {810 schedulerEvent = window.event;811}812813export function resolveEventType(): null | string {814 const event = window.event;815 return event && event !== schedulerEvent ? event.type : null;816}817818export function resolveEventTimeStamp(): number {819 const event = window.event;820 return event && event !== schedulerEvent ? event.timeStamp : -1.1;821}822823export const isPrimaryRenderer = true;824export const warnsIfNotActing = true;825// This initialization code may run even on server environments826// if a component just imports ReactDOM (e.g. for findDOMNode).827// Some environments might not have setTimeout or clearTimeout.828export const scheduleTimeout: any =829 typeof setTimeout === 'function' ? setTimeout : (undefined as any);830export const cancelTimeout: any =831 typeof clearTimeout === 'function' ? clearTimeout : (undefined as any);832export const noTimeout: -1 = -1;833const localPromise = typeof Promise === 'function' ? Promise : undefined;834const localRequestAnimationFrame =835 typeof requestAnimationFrame === 'function'836 ? requestAnimationFrame837 : scheduleTimeout;838839export {getClosestInstanceFromNode as getInstanceFromNode};840841export function preparePortalMount(portalInstance: Instance): void {842 listenToAllSupportedEvents(portalInstance);843}844845export function prepareScopeUpdate(846 scopeInstance: ReactScopeInstance,847 internalInstanceHandle: Object,848): void {849 if (enableScopeAPI) {850 precacheFiberNode(internalInstanceHandle, scopeInstance);851 }852}853854export function getInstanceFromScope(855 scopeInstance: ReactScopeInstance,856): null | Object {857 if (enableScopeAPI) {858 return getFiberFromScopeInstance(scopeInstance);859 }860 return null;861}862863// -------------------864// Microtasks865// -------------------866export const supportsMicrotasks: boolean = true;867export const scheduleMicrotask: any =868 typeof queueMicrotask === 'function'869 ? queueMicrotask870 : typeof localPromise !== 'undefined'871 ? callback =>872 localPromise.resolve(null).then(callback).catch(handleErrorInNextTick)873 : scheduleTimeout; // TODO: Determine the best fallback here.874875function handleErrorInNextTick(error: any) {876 setTimeout(() => {877 throw error;878 });879}880881// -------------------882// Mutation883// -------------------884885export const supportsMutation: boolean = true;886887export function commitMount(888 domElement: Instance,889 type: string,890 newProps: Props,891 internalInstanceHandle: Object,892): void {893 // Despite the naming that might imply otherwise, this method only894 // fires if there is an `Update` effect scheduled during mounting.895 // This happens if `finalizeInitialChildren` returns `true` (which it896 // does to implement the `autoFocus` attribute on the client). But897 // there are also other cases when this might happen (such as patching898 // up text content during hydration mismatch). So we'll check this again.899 switch (type) {900 case 'button':901 case 'input':902 case 'select':903 case 'textarea':904 if (newProps.autoFocus) {905 (906 domElement as any as907 | HTMLButtonElement908 | HTMLInputElement909 | HTMLSelectElement910 | HTMLTextAreaElement911 ).focus();912 }913 return;914 case 'img': {915 // The technique here is to assign the src or srcSet property to cause the browser916 // to issue a new load event. If it hasn't loaded yet it'll fire whenever the load actually completes.917 // If it has already loaded we missed it so the second load will still be the first one that executes918 // any associated onLoad props.919 // Even if we have srcSet we prefer to reassign src. The reason is that Firefox does not trigger a new920 // load event when only srcSet is assigned. Chrome will trigger a load event if either is assigned so we921 // only need to assign one. And Safari just never triggers a new load event which means this technique922 // is already a noop regardless of which properties are assigned. We should revisit if browsers update923 // this heuristic in the future.924 if (newProps.src) {925 const src = (newProps as any).src;926 if (enableSrcObject && typeof src === 'object') {927 // For object src, we can't just set the src again to the same blob URL because it might have928 // already revoked if it loaded before this. However, we can create a new blob URL and set that.929 // This is relatively cheap since the blob is already in memory but this might cause some930 // duplicated work.931 // TODO: We could maybe detect if load hasn't fired yet and if so reuse the URL.932 try {933 setSrcObject(domElement, type, src);934 return;935 } catch (x) {936 // If URL.createObjectURL() errors, it was probably some other object type937 // that should be toString:ed instead, so we just fall-through to the normal938 // path.939 }940 }941 (domElement as any as HTMLImageElement).src = src;942 } else if (newProps.srcSet) {943 (domElement as any as HTMLImageElement).srcset = (944 newProps as any945 ).srcSet;946 }947 return;948 }949 }950}951952export function commitHydratedInstance(953 domElement: Instance,954 type: string,955 props: Props,956 internalInstanceHandle: Object,957): void {958 if (!enableHydrationChangeEvent) {959 return;960 }961 // This fires in the commit phase if a hydrated instance needs to do further962 // work in the commit phase. Similar to commitMount. However, this should not963 // do things that would've already happened such as set auto focus since that964 // would steal focus. It's only scheduled if finalizeHydratedChildren returns965 // true.966 switch (type) {967 case 'input': {968 hydrateInput(969 domElement,970 props.value,971 props.defaultValue,972 props.checked,973 props.defaultChecked,974 );975 break;976 }977 case 'select': {978 hydrateSelect(979 domElement,980 props.value,981 props.defaultValue,982 props.multiple,983 );984 break;985 }986 case 'textarea':987 hydrateTextarea(domElement, props.value, props.defaultValue);988 break;989 case 'img':990 // TODO: Should we replay onLoad events?991 break;992 }993}994995export function commitUpdate(996 domElement: Instance,997 type: string,998 oldProps: Props,999 newProps: Props,1000 internalInstanceHandle: Object,1001): void {1002 // Diff and update the properties.1003 updateProperties(domElement, type, oldProps, newProps);10041005 // Update the props handle so that we know which props are the ones with1006 // with current event handlers.1007 updateFiberProps(domElement, newProps);1008}10091010export function resetTextContent(domElement: Instance): void {1011 setTextContent(domElement, '');1012}10131014export function commitTextUpdate(1015 textInstance: TextInstance,1016 oldText: string,1017 newText: string,1018): void {1019 textInstance.nodeValue = newText;1020}10211022const supportsMoveBefore =1023 // $FlowFixMe[prop-missing]: We're doing the feature detection here.1024 enableMoveBefore &&1025 typeof window !== 'undefined' &&1026 typeof window.Element.prototype.moveBefore === 'function';10271028export function appendChild(1029 parentInstance: Instance,1030 child: Instance | TextInstance,1031): void {1032 if (supportsMoveBefore && child.parentNode !== null) {1033 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1034 parentInstance.moveBefore(child, null);1035 } else {1036 parentInstance.appendChild(child);1037 }1038}10391040function warnForReactChildrenConflict(container: Container): void {1041 if (__DEV__) {1042 if ((container as any).__reactWarnedAboutChildrenConflict) {1043 return;1044 }1045 const props = getFiberCurrentPropsFromNode(container);1046 if (props !== null) {1047 const fiber = getInstanceFromNode(container);1048 if (fiber !== null) {1049 if (1050 typeof props.children === 'string' ||1051 typeof props.children === 'number'1052 ) {1053 (container as any).__reactWarnedAboutChildrenConflict = true;1054 // Run the warning with the Fiber of the container for context of where the children are specified.1055 // We could also maybe use the Portal. The current execution context is the child being added.1056 runWithFiberInDEV(fiber, () => {1057 console.error(1058 'Cannot use a ref on a React element as a container to `createRoot` or `createPortal` ' +1059 'if that element also sets "children" text content using React. It should be a leaf with no children. ' +1060 "Otherwise it's ambiguous which children should be used.",1061 );1062 });1063 } else if (props.dangerouslySetInnerHTML != null) {1064 (container as any).__reactWarnedAboutChildrenConflict = true;1065 runWithFiberInDEV(fiber, () => {1066 console.error(1067 'Cannot use a ref on a React element as a container to `createRoot` or `createPortal` ' +1068 'if that element also sets "dangerouslySetInnerHTML" using React. It should be a leaf with no children. ' +1069 "Otherwise it's ambiguous which children should be used.",1070 );1071 });1072 }1073 }1074 }1075 }1076}10771078export function appendChildToContainer(1079 container: Container,1080 child: Instance | TextInstance,1081): void {1082 if (__DEV__) {1083 warnForReactChildrenConflict(container);1084 }1085 let parentNode: DocumentFragment | Element;1086 if (container.nodeType === DOCUMENT_NODE) {1087 parentNode = (container as any).body;1088 } else if (1089 !disableCommentsAsDOMContainers &&1090 container.nodeType === COMMENT_NODE1091 ) {1092 parentNode = container.parentNode as any;1093 if (supportsMoveBefore && child.parentNode !== null) {1094 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1095 parentNode.moveBefore(child, container);1096 } else {1097 parentNode.insertBefore(child, container);1098 }1099 return;1100 } else if (container.nodeName === 'HTML') {1101 parentNode = container.ownerDocument.body as any;1102 } else {1103 parentNode = container as any;1104 }1105 if (supportsMoveBefore && child.parentNode !== null) {1106 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1107 parentNode.moveBefore(child, null);1108 } else {1109 parentNode.appendChild(child);1110 }11111112 // This container might be used for a portal.1113 // If something inside a portal is clicked, that click should bubble1114 // through the React tree. However, on Mobile Safari the click would1115 // never bubble through the *DOM* tree unless an ancestor with onclick1116 // event exists. So we wouldn't see it and dispatch it.1117 // This is why we ensure that non React root containers have inline onclick1118 // defined.1119 // https://github.com/react/react/issues/119181120 const reactRootContainer = container._reactRootContainer;1121 if (1122 // $FlowFixMe[invalid-compare]1123 (reactRootContainer === null || reactRootContainer === undefined) &&1124 parentNode.onclick === null1125 ) {1126 // TODO: This cast may not be sound for SVG, MathML or custom elements.1127 trapClickOnNonInteractiveElement(parentNode as any as HTMLElement);1128 }1129}11301131export function insertBefore(1132 parentInstance: Instance,1133 child: Instance | TextInstance,1134 beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,1135): void {1136 if (supportsMoveBefore && child.parentNode !== null) {1137 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1138 parentInstance.moveBefore(child, beforeChild);1139 } else {1140 parentInstance.insertBefore(child, beforeChild);1141 }1142}11431144export function insertInContainerBefore(1145 container: Container,1146 child: Instance | TextInstance,1147 beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,1148): void {1149 if (__DEV__) {1150 warnForReactChildrenConflict(container);1151 }1152 let parentNode: DocumentFragment | Element;1153 if (container.nodeType === DOCUMENT_NODE) {1154 parentNode = (container as any).body;1155 } else if (1156 !disableCommentsAsDOMContainers &&1157 container.nodeType === COMMENT_NODE1158 ) {1159 parentNode = container.parentNode as any;1160 } else if (container.nodeName === 'HTML') {1161 parentNode = container.ownerDocument.body as any;1162 } else {1163 parentNode = container as any;1164 }1165 if (supportsMoveBefore && child.parentNode !== null) {1166 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1167 parentNode.moveBefore(child, beforeChild);1168 } else {1169 parentNode.insertBefore(child, beforeChild);1170 }1171}11721173export function isSingletonScope(type: string): boolean {1174 return type === 'head';1175}11761177function createEvent(type: DOMEventName, bubbles: boolean): Event {1178 const event = document.createEvent('Event');1179 event.initEvent(type as any as string, bubbles, false);1180 return event;1181}11821183function dispatchBeforeDetachedBlur(1184 target: HTMLElement,1185 internalInstanceHandle: Object,1186): void {1187 if (enableCreateEventHandleAPI) {1188 const event = createEvent('beforeblur', true);1189 // Dispatch "beforeblur" directly on the target,1190 // so it gets picked up by the event system and1191 // can propagate through the React internal tree.1192 // $FlowFixMe[prop-missing]: internal field1193 event._detachedInterceptFiber = internalInstanceHandle;1194 target.dispatchEvent(event);1195 }1196}11971198function dispatchAfterDetachedBlur(target: HTMLElement): void {1199 if (enableCreateEventHandleAPI) {1200 const event = createEvent('afterblur', false);1201 // So we know what was detached, make the relatedTarget the1202 // detached target on the "afterblur" event.1203 (event as any).relatedTarget = target;1204 // Dispatch the event on the document.1205 document.dispatchEvent(event);1206 }1207}12081209export function removeChild(1210 parentInstance: Instance,1211 child: Instance | TextInstance | SuspenseInstance | ActivityInstance,1212): void {1213 parentInstance.removeChild(child);1214}12151216export function removeChildFromContainer(1217 container: Container,1218 child: Instance | TextInstance | SuspenseInstance | ActivityInstance,1219): void {1220 let parentNode: DocumentFragment | Element;1221 if (container.nodeType === DOCUMENT_NODE) {1222 parentNode = (container as any).body;1223 } else if (1224 !disableCommentsAsDOMContainers &&1225 container.nodeType === COMMENT_NODE1226 ) {1227 parentNode = container.parentNode as any;1228 } else if (container.nodeName === 'HTML') {1229 parentNode = container.ownerDocument.body as any;1230 } else {1231 parentNode = container as any;1232 }1233 parentNode.removeChild(child);1234}12351236function clearHydrationBoundary(1237 parentInstance: Instance,1238 hydrationInstance: SuspenseInstance | ActivityInstance,1239): void {1240 let node: Node = hydrationInstance;1241 // Delete all nodes within this suspense boundary.1242 // There might be nested nodes so we need to keep track of how1243 // deep we are and only break out when we're back on top.1244 let depth = 0;1245 do {1246 const nextNode = node.nextSibling;1247 parentInstance.removeChild(node);1248 if (nextNode && nextNode.nodeType === COMMENT_NODE) {1249 const data = (nextNode as any).data as string;1250 if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {1251 if (depth === 0) {1252 parentInstance.removeChild(nextNode);1253 // Retry if any event replaying was blocked on this.1254 retryIfBlockedOn(hydrationInstance);1255 return;1256 } else {1257 depth--;1258 }1259 } else if (1260 data === SUSPENSE_START_DATA ||1261 data === SUSPENSE_PENDING_START_DATA ||1262 data === SUSPENSE_QUEUED_START_DATA ||1263 data === SUSPENSE_FALLBACK_START_DATA ||1264 data === ACTIVITY_START_DATA1265 ) {1266 depth++;1267 } else if (data === PREAMBLE_CONTRIBUTION_HTML) {1268 // If a preamble contribution marker is found within the bounds of this boundary,1269 // then it contributed to the html tag and we need to reset it.1270 const ownerDocument = parentInstance.ownerDocument;1271 const documentElement: Element = ownerDocument.documentElement as any;1272 clearSingletonPreambleContribution(documentElement);1273 } else if (data === PREAMBLE_CONTRIBUTION_HEAD) {1274 const ownerDocument = parentInstance.ownerDocument;1275 const head: Element = ownerDocument.head as any;1276 clearSingletonPreambleContribution(head);1277 // We need to clear the head because this is the only singleton that can have children that1278 // were part of this boundary but are not inside this boundary.1279 clearHead(head);1280 } else if (data === PREAMBLE_CONTRIBUTION_BODY) {1281 const ownerDocument = parentInstance.ownerDocument;1282 const body: Element = ownerDocument.body as any;1283 clearSingletonPreambleContribution(body);1284 }1285 }1286 // $FlowFixMe[incompatible-type] we bail out when we get a null1287 node = nextNode;1288 } while (node);1289 // TODO: Warn, we didn't find the end comment boundary.1290 // Retry if any event replaying was blocked on this.1291 retryIfBlockedOn(hydrationInstance);1292}12931294export function clearActivityBoundary(1295 parentInstance: Instance,1296 activityInstance: ActivityInstance,1297): void {1298 clearHydrationBoundary(parentInstance, activityInstance);1299}13001301export function clearSuspenseBoundary(1302 parentInstance: Instance,1303 suspenseInstance: SuspenseInstance,1304): void {1305 clearHydrationBoundary(parentInstance, suspenseInstance);1306}13071308function clearHydrationBoundaryFromContainer(1309 container: Container,1310 hydrationInstance: SuspenseInstance | ActivityInstance,1311): void {1312 let parentNode: DocumentFragment | Element;1313 if (container.nodeType === DOCUMENT_NODE) {1314 parentNode = (container as any).body;1315 } else if (1316 !disableCommentsAsDOMContainers &&1317 container.nodeType === COMMENT_NODE1318 ) {1319 parentNode = container.parentNode as any;1320 } else if (container.nodeName === 'HTML') {1321 parentNode = container.ownerDocument.body as any;1322 } else {1323 parentNode = container as any;1324 }1325 clearHydrationBoundary(parentNode, hydrationInstance);1326 // Retry if any event replaying was blocked on this.1327 retryIfBlockedOn(container);1328}13291330export function clearActivityBoundaryFromContainer(1331 container: Container,1332 activityInstance: ActivityInstance,1333): void {1334 clearHydrationBoundaryFromContainer(container, activityInstance);1335}13361337export function clearSuspenseBoundaryFromContainer(1338 container: Container,1339 suspenseInstance: SuspenseInstance,1340): void {1341 clearHydrationBoundaryFromContainer(container, suspenseInstance);1342}13431344function hideOrUnhideDehydratedBoundary(1345 suspenseInstance: SuspenseInstance | ActivityInstance,1346 isHidden: boolean,1347) {1348 let node: Node = suspenseInstance;1349 // Unhide all nodes within this suspense boundary.1350 let depth = 0;1351 do {1352 const nextNode = node.nextSibling;1353 if (node.nodeType === ELEMENT_NODE) {1354 const instance = node as any as HTMLElement & {_stashedDisplay?: string};1355 if (isHidden) {1356 instance._stashedDisplay = instance.style.display;1357 instance.style.display = 'none';1358 } else {1359 instance.style.display = instance._stashedDisplay || '';1360 if (instance.getAttribute('style') === '') {1361 instance.removeAttribute('style');1362 }1363 }1364 } else if (node.nodeType === TEXT_NODE) {1365 const textNode = node as any as Text & {_stashedText?: string};1366 if (isHidden) {1367 textNode._stashedText = textNode.nodeValue;1368 textNode.nodeValue = '';1369 } else {1370 textNode.nodeValue = textNode._stashedText || '';1371 }1372 }1373 if (nextNode && nextNode.nodeType === COMMENT_NODE) {1374 const data = (nextNode as any).data as string;1375 if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {1376 if (depth === 0) {1377 return;1378 } else {1379 depth--;1380 }1381 } else if (1382 data === SUSPENSE_START_DATA ||1383 data === SUSPENSE_PENDING_START_DATA ||1384 data === SUSPENSE_QUEUED_START_DATA ||1385 data === SUSPENSE_FALLBACK_START_DATA ||1386 data === ACTIVITY_START_DATA1387 ) {1388 depth++;1389 }1390 // TODO: Should we hide preamble contribution in this case?1391 }1392 // $FlowFixMe[incompatible-type] we bail out when we get a null1393 node = nextNode;1394 } while (node);1395}13961397export function hideDehydratedBoundary(1398 suspenseInstance: SuspenseInstance,1399): void {1400 hideOrUnhideDehydratedBoundary(suspenseInstance, true);1401}14021403export function hideInstance(instance: Instance): void {1404 // TODO: Does this work for all element types? What about MathML? Should we1405 // pass host context to this method?1406 instance = instance as any as HTMLElement;1407 const style = instance.style;1408 // $FlowFixMe[method-unbinding]1409 if (typeof style.setProperty === 'function') {1410 style.setProperty('display', 'none', 'important');1411 } else {1412 style.display = 'none';1413 }1414}14151416export function hideTextInstance(textInstance: TextInstance): void {1417 textInstance.nodeValue = '';1418}14191420export function unhideDehydratedBoundary(1421 dehydratedInstance: SuspenseInstance | ActivityInstance,1422): void {1423 hideOrUnhideDehydratedBoundary(dehydratedInstance, false);1424}14251426export function unhideInstance(instance: Instance, props: Props): void {1427 instance = instance as any as HTMLElement;1428 const styleProp = props[STYLE];1429 const display =1430 styleProp !== undefined &&1431 // $FlowFixMe[invalid-compare]1432 styleProp !== null &&1433 styleProp.hasOwnProperty('display')1434 ? styleProp.display1435 : null;1436 instance.style.display =1437 display == null || typeof display === 'boolean'1438 ? ''1439 : // The value would've errored already if it wasn't safe.1440 // eslint-disable-next-line react-internal/safe-string-coercion1441 ('' + display).trim();1442}14431444export function unhideTextInstance(1445 textInstance: TextInstance,1446 text: string,1447): void {1448 textInstance.nodeValue = text;1449}14501451function warnForBlockInsideInline(instance: HTMLElement) {1452 if (__DEV__) {1453 let nextNode = instance.firstChild;1454 outer: while (nextNode != null) {1455 let node: Node = nextNode;1456 if (1457 node.nodeType === ELEMENT_NODE &&1458 getComputedStyle(node as any).display === 'block'1459 ) {1460 const fiber =1461 getInstanceFromNode(node) || getInstanceFromNode(instance);1462 runWithFiberInDEV(1463 fiber,1464 (parentTag: string, childTag: string) => {1465 console.error(1466 "You're about to start a <ViewTransition> around a display: inline " +1467 'element <%s>, which itself has a display: block element <%s> inside it. ' +1468 'This might trigger a bug in Safari which causes the View Transition to ' +1469 'be skipped with a duplicate name error.\n' +1470 'https://bugs.webkit.org/show_bug.cgi?id=290923',1471 parentTag.toLocaleLowerCase(),1472 childTag.toLocaleLowerCase(),1473 );1474 },1475 instance.tagName,1476 (node as any).tagName,1477 );1478 break;1479 }1480 if (node.firstChild != null) {1481 nextNode = node.firstChild;1482 continue;1483 }1484 if (node === instance) {1485 break;1486 }1487 while (node.nextSibling == null) {1488 if (node.parentNode == null || node.parentNode === instance) {1489 break;1490 }1491 node = node.parentNode;1492 }1493 nextNode = node.nextSibling;1494 }1495 }1496}14971498function countClientRects(rects: Array<ClientRect>): number {1499 if (rects.length === 1) {1500 return 1;1501 }1502 // Count non-zero rects.1503 let count = 0;1504 for (let i = 0; i < rects.length; i++) {1505 const rect = rects[i];1506 if (rect.width > 0 && rect.height > 0) {1507 count++;1508 }1509 }1510 return count;1511}15121513export function applyViewTransitionName(1514 instance: Instance,1515 name: string,1516 className: ?string,1517): void {1518 instance = instance as any as HTMLElement;1519 // If the name isn't valid CSS identifier, base64 encode the name instead.1520 // This doesn't let you select it in custom CSS selectors but it does work in current1521 // browsers.1522 const escapedName =1523 CSS.escape(name) !== name ? 'r-' + btoa(name).replace(/=/g, '') : name;1524 // $FlowFixMe[prop-missing]1525 instance.style.viewTransitionName = escapedName;1526 if (className != null) {1527 // $FlowFixMe[prop-missing]1528 instance.style.viewTransitionClass = className;1529 }1530 const computedStyle = getComputedStyle(instance);1531 if (computedStyle.display === 'inline') {1532 // WebKit has a bug where assigning a name to display: inline elements errors1533 // if they have display: block children. We try to work around this bug in the1534 // simple case by converting it automatically to display: inline-block.1535 // https://bugs.webkit.org/show_bug.cgi?id=2909231536 const rects = instance.getClientRects();1537 if (1538 // $FlowFixMe[incompatible-type]1539 countClientRects(rects) === 11540 ) {1541 // If the instance has a single client rect, that means that it can be1542 // expressed as a display: inline-block or block.1543 // This will cause layout thrash but we live with it since inline view transitions1544 // are unusual.1545 const style = instance.style;1546 // If there's literally only one rect, then it's likely on a single line like an1547 // inline-block. If it's multiple rects but all but one of them are empty it's1548 // likely because it's a single block that caused a line break.1549 style.display = rects.length === 1 ? 'inline-block' : 'block';1550 // Margin doesn't apply to inline so should be zero. However, padding top/bottom1551 // applies to inline-block positioning which we can offset by setting the margin1552 // to the negative padding to get it back into original position.1553 style.marginTop = '-' + computedStyle.paddingTop;1554 style.marginBottom = '-' + computedStyle.paddingBottom;1555 } else {1556 // This case cannot be easily fixed if it has blocks but it's also fine if1557 // it doesn't have blocks. So we only warn in DEV about this being an issue.1558 warnForBlockInsideInline(instance);1559 }1560 }1561}15621563export function restoreViewTransitionName(1564 instance: Instance,1565 props: Props,1566): void {1567 instance = instance as any as HTMLElement;1568 const style = instance.style;1569 const styleProp = props[STYLE];1570 const viewTransitionName =1571 styleProp != null1572 ? styleProp.hasOwnProperty('viewTransitionName')1573 ? styleProp.viewTransitionName1574 : styleProp.hasOwnProperty('view-transition-name')1575 ? styleProp['view-transition-name']1576 : null1577 : null;1578 // $FlowFixMe[prop-missing]1579 style.viewTransitionName =1580 viewTransitionName == null || typeof viewTransitionName === 'boolean'1581 ? ''1582 : // The value would've errored already if it wasn't safe.1583 // eslint-disable-next-line react-internal/safe-string-coercion1584 ('' + viewTransitionName).trim();1585 const viewTransitionClass =1586 styleProp != null1587 ? styleProp.hasOwnProperty('viewTransitionClass')1588 ? styleProp.viewTransitionClass1589 : styleProp.hasOwnProperty('view-transition-class')1590 ? styleProp['view-transition-class']1591 : null1592 : null;1593 // $FlowFixMe[prop-missing]1594 style.viewTransitionClass =1595 viewTransitionClass == null || typeof viewTransitionClass === 'boolean'1596 ? ''1597 : // The value would've errored already if it wasn't safe.1598 // eslint-disable-next-line react-internal/safe-string-coercion1599 ('' + viewTransitionClass).trim();1600 if (style.display === 'inline-block') {1601 // We might have overridden the style. Reset it to what it should be.1602 if (styleProp == null) {1603 style.display = style.margin = '';1604 } else {1605 const display = styleProp.display;1606 style.display =1607 display == null || typeof display === 'boolean' ? '' : display;1608 const margin = styleProp.margin;1609 if (margin != null) {1610 style.margin = margin;1611 } else {1612 const marginTop = styleProp.hasOwnProperty('marginTop')1613 ? styleProp.marginTop1614 : styleProp['margin-top'];1615 style.marginTop =1616 marginTop == null || typeof marginTop === 'boolean' ? '' : marginTop;1617 const marginBottom = styleProp.hasOwnProperty('marginBottom')1618 ? styleProp.marginBottom1619 : styleProp['margin-bottom'];1620 style.marginBottom =1621 marginBottom == null || typeof marginBottom === 'boolean'1622 ? ''1623 : marginBottom;1624 }1625 }1626 }1627}16281629export function cancelViewTransitionName(1630 instance: Instance,1631 oldName: string,1632 props: Props,1633): void {1634 // To cancel the "new" state and paint this instance as part of the parent, all we have to do1635 // is remove the view-transition-name before we exit startViewTransition.1636 restoreViewTransitionName(instance, props);1637 // There isn't a way to cancel an "old" state but what we can do is hide it by animating it.1638 // Since it is already removed from the old state of the parent, this technique only works1639 // if the parent also isn't transitioning. Therefore we should only cancel the root most1640 // ViewTransitions.1641 const documentElement = instance.ownerDocument.documentElement;1642 if (documentElement !== null) {1643 documentElement.animate(1644 {opacity: [0, 0], pointerEvents: ['none', 'none']},1645 // $FlowFixMe[incompatible-type]1646 {1647 duration: 0,1648 fill: 'forwards',1649 pseudoElement: '::view-transition-group(' + oldName + ')',1650 },1651 );1652 }1653}16541655export function cancelRootViewTransitionName(rootContainer: Container): void {1656 const documentElement: null | HTMLElement =1657 rootContainer.nodeType === DOCUMENT_NODE1658 ? (rootContainer as any).documentElement1659 : rootContainer.ownerDocument.documentElement;16601661 if (1662 !disableCommentsAsDOMContainers &&1663 rootContainer.nodeType === COMMENT_NODE1664 ) {1665 if (__DEV__) {1666 console.warn(1667 'Cannot cancel root view transition on a comment node. All view transitions will be globally scoped.',1668 );1669 }1670 return;1671 }16721673 if (1674 documentElement !== null &&1675 // $FlowFixMe[prop-missing]1676 documentElement.style.viewTransitionName === ''1677 ) {1678 // $FlowFixMe[prop-missing]1679 documentElement.style.viewTransitionName = 'none';1680 documentElement.animate(1681 {opacity: [0, 0], pointerEvents: ['none', 'none']},1682 // $FlowFixMe[incompatible-type]1683 {1684 duration: 0,1685 fill: 'forwards',1686 pseudoElement: '::view-transition-group(root)',1687 },1688 );1689 // By default the root ::view-transition selector captures all pointer events,1690 // which means nothing gets interactive. We want to let whatever is not animating1691 // remain interactive during the transition. To do that, we set the size to nothing1692 // so that the transition doesn't capture any clicks. We don't set pointer-events1693 // on this one as that would apply to all running transitions. This lets animations1694 // that are running to block clicks so that they don't end up incorrectly hitting1695 // whatever is below the animation.1696 documentElement.animate(1697 {width: [0, 0], height: [0, 0]},1698 // $FlowFixMe[incompatible-call]1699 // $FlowFixMe[incompatible-type]1700 {1701 duration: 0,1702 fill: 'forwards',1703 pseudoElement: '::view-transition',1704 },1705 );1706 }1707}17081709export function restoreRootViewTransitionName(rootContainer: Container): void {1710 let containerInstance: Instance;1711 if (rootContainer.nodeType === DOCUMENT_NODE) {1712 containerInstance = (rootContainer as any).body;1713 } else if (rootContainer.nodeName === 'HTML') {1714 containerInstance = rootContainer.ownerDocument.body as any;1715 } else {1716 // If the container is not the whole document, then we ideally should probably1717 // clone the whole document outside of the React too.1718 containerInstance = rootContainer as any;1719 }1720 if (1721 !disableCommentsAsDOMContainers &&1722 containerInstance.nodeType === COMMENT_NODE1723 ) {1724 return;1725 }1726 if (1727 // $FlowFixMe[prop-missing]1728 containerInstance.style.viewTransitionName === 'root'1729 ) {1730 // If we moved the root view transition name to the container in a gesture1731 // we need to restore it now.1732 containerInstance.style.viewTransitionName = '';1733 }1734 const documentElement: null | HTMLElement =1735 containerInstance.ownerDocument.documentElement;1736 if (1737 documentElement !== null &&1738 // $FlowFixMe[prop-missing]1739 documentElement.style.viewTransitionName === 'none'1740 ) {1741 // $FlowFixMe[prop-missing]1742 documentElement.style.viewTransitionName = '';1743 }1744}17451746function getComputedTransform(style: CSSStyleDeclaration): string {1747 // Gets the merged transform of all the short hands.1748 const computedStyle: any = style;1749 let transform: string = computedStyle.transform;1750 if (transform === 'none') {1751 transform = '';1752 }1753 const scale: string = computedStyle.scale;1754 if (scale !== 'none' && scale !== '') {1755 const parts = scale.split(' ');1756 transform =1757 (parts.length === 3 ? 'scale3d' : 'scale') +1758 '(' +1759 parts.join(', ') +1760 ') ' +1761 transform;1762 }1763 const rotate: string = computedStyle.rotate;1764 if (rotate !== 'none' && rotate !== '') {1765 const parts = rotate.split(' ');1766 if (parts.length === 1) {1767 transform = 'rotate(' + parts[0] + ') ' + transform;1768 } else if (parts.length === 2) {1769 transform =1770 'rotate' + parts[0].toUpperCase() + '(' + parts[1] + ') ' + transform;1771 } else {1772 transform = 'rotate3d(' + parts.join(', ') + ') ' + transform;1773 }1774 }1775 const translate: string = computedStyle.translate;1776 if (translate !== 'none' && translate !== '') {1777 const parts = translate.split(' ');1778 transform =1779 (parts.length === 3 ? 'translate3d' : 'translate') +1780 '(' +1781 parts.join(', ') +1782 ') ' +1783 transform;1784 }1785 return transform;1786}17871788function moveOutOfViewport(1789 originalStyle: CSSStyleDeclaration,1790 element: HTMLElement,1791): void {1792 // Apply a transform that safely puts the whole element outside the viewport1793 // while still letting it paint its "old" state to a snapshot.1794 const transform = getComputedTransform(originalStyle);1795 // Clear the long form properties.1796 // $FlowFixMe[prop-missing]1797 element.style.translate = 'none';1798 // $FlowFixMe[prop-missing]1799 element.style.scale = 'none';1800 // $FlowFixMe[prop-missing]1801 element.style.rotate = 'none';1802 // Apply a translate to move it way out of the viewport. This is applied first1803 // so that it is in the coordinate space of the parent and not after applying1804 // other transforms. That's why we need to merge the long form properties.1805 // TODO: Ideally we'd adjust for the parent's rotate/scale. Otherwise when1806 // we move back the ::view-transition-group we might overshoot or undershoot.1807 element.style.transform = 'translate(-20000px, -20000px) ' + transform;1808}18091810function moveOldFrameIntoViewport(keyframe: any): void {1811 // In the resulting View Transition Animation, the first frame will be offset.1812 const computedTransform: ?string = keyframe.transform;1813 if (computedTransform != null) {1814 let transform = computedTransform === 'none' ? '' : computedTransform;1815 transform = 'translate(20000px, 20000px) ' + transform;1816 keyframe.transform = transform;1817 }1818}18191820export function cloneRootViewTransitionContainer(1821 rootContainer: Container,1822): Instance {1823 // This implies that we're not going to animate the root document but instead1824 // the clone so we first clear the name of the root container.1825 const documentElement: null | HTMLElement =1826 rootContainer.nodeType === DOCUMENT_NODE1827 ? (rootContainer as any).documentElement1828 : rootContainer.ownerDocument.documentElement;1829 if (1830 documentElement !== null &&1831 // $FlowFixMe[prop-missing]1832 documentElement.style.viewTransitionName === ''1833 ) {1834 // $FlowFixMe[prop-missing]1835 documentElement.style.viewTransitionName = 'none';1836 }18371838 let containerInstance: HTMLElement;1839 if (rootContainer.nodeType === DOCUMENT_NODE) {1840 containerInstance = (rootContainer as any).body;1841 } else if (rootContainer.nodeName === 'HTML') {1842 containerInstance = rootContainer.ownerDocument.body as any;1843 } else if (1844 !disableCommentsAsDOMContainers &&1845 rootContainer.nodeType === COMMENT_NODE1846 ) {1847 throw new Error(1848 'Cannot use a startGestureTransition() with a comment node root.',1849 );1850 } else {1851 // If the container is not the whole document, then we ideally should probably1852 // clone the whole document outside of the React too.1853 containerInstance = rootContainer as any;1854 }18551856 const containerParent = containerInstance.parentNode;1857 if (containerParent === null) {1858 throw new Error(1859 'Cannot use a startGestureTransition() on a detached root.',1860 );1861 }18621863 const clone: HTMLElement = containerInstance.cloneNode(false);18641865 const computedStyle = getComputedStyle(containerInstance);18661867 if (1868 computedStyle.position === 'absolute' ||1869 computedStyle.position === 'fixed'1870 ) {1871 // If the style is already absolute, we don't have to do anything because it'll appear1872 // in the same place.1873 } else {1874 // Otherwise we need to absolutely position the clone in the same location as the original.1875 let positionedAncestor: HTMLElement = containerParent;1876 while (1877 positionedAncestor.parentNode != null &&1878 positionedAncestor.parentNode.nodeType !== DOCUMENT_NODE1879 ) {1880 if (getComputedStyle(positionedAncestor).position !== 'static') {1881 break;1882 }1883 // $FlowFixMe[incompatible-type]: This is refined.1884 positionedAncestor = positionedAncestor.parentNode;1885 }18861887 const positionedAncestorStyle: any = positionedAncestor.style;1888 const containerInstanceStyle: any = containerInstance.style;1889 // Clear the transform while we're measuring since it affects the bounding client rect.1890 const prevAncestorTranslate = positionedAncestorStyle.translate;1891 const prevAncestorScale = positionedAncestorStyle.scale;1892 const prevAncestorRotate = positionedAncestorStyle.rotate;1893 const prevAncestorTransform = positionedAncestorStyle.transform;1894 const prevTranslate = containerInstanceStyle.translate;1895 const prevScale = containerInstanceStyle.scale;1896 const prevRotate = containerInstanceStyle.rotate;1897 const prevTransform = containerInstanceStyle.transform;1898 positionedAncestorStyle.translate = 'none';1899 positionedAncestorStyle.scale = 'none';1900 positionedAncestorStyle.rotate = 'none';1901 positionedAncestorStyle.transform = 'none';1902 containerInstanceStyle.translate = 'none';1903 containerInstanceStyle.scale = 'none';1904 containerInstanceStyle.rotate = 'none';1905 containerInstanceStyle.transform = 'none';19061907 const ancestorRect = positionedAncestor.getBoundingClientRect();1908 const rect = containerInstance.getBoundingClientRect();19091910 const cloneStyle = clone.style;1911 cloneStyle.position = 'absolute';1912 cloneStyle.top = rect.top - ancestorRect.top + 'px';1913 cloneStyle.left = rect.left - ancestorRect.left + 'px';1914 cloneStyle.width = rect.width + 'px';1915 cloneStyle.height = rect.height + 'px';1916 cloneStyle.margin = '0px';1917 cloneStyle.boxSizing = 'border-box';19181919 positionedAncestorStyle.translate = prevAncestorTranslate;1920 positionedAncestorStyle.scale = prevAncestorScale;1921 positionedAncestorStyle.rotate = prevAncestorRotate;1922 positionedAncestorStyle.transform = prevAncestorTransform;1923 containerInstanceStyle.translate = prevTranslate;1924 containerInstanceStyle.scale = prevScale;1925 containerInstanceStyle.rotate = prevRotate;1926 containerInstanceStyle.transform = prevTransform;1927 }19281929 // For this transition the container will act as the root. Nothing outside of it should1930 // be affected anyway. This lets us transition from the cloned container to the original.1931 // $FlowFixMe[prop-missing]1932 clone.style.viewTransitionName = 'root';19331934 // Move out of the viewport so that it's still painted for the snapshot but is not visible1935 // for the frame where the snapshot happens.1936 moveOutOfViewport(computedStyle, clone);19371938 // Insert the clone after the root container as a sibling. This may inject a body1939 // as the next sibling of an existing body. document.body will still point to the1940 // first one and any id selectors will still find the first one. That's why it's1941 // important that it's after the existing node.1942 containerInstance.parentNode.insertBefore(1943 clone,1944 containerInstance.nextSibling,1945 );19461947 return clone;1948}19491950export function removeRootViewTransitionClone(1951 rootContainer: Container,1952 clone: Instance,1953): void {1954 let containerInstance: Instance;1955 if (rootContainer.nodeType === DOCUMENT_NODE) {1956 containerInstance = (rootContainer as any).body;1957 } else if (rootContainer.nodeName === 'HTML') {1958 containerInstance = rootContainer.ownerDocument.body as any;1959 } else {1960 // If the container is not the whole document, then we ideally should probably1961 // clone the whole document outside of the React too.1962 containerInstance = rootContainer as any;1963 }1964 const containerParent = containerInstance.parentNode;1965 if (containerParent === null) {1966 throw new Error(1967 'Cannot use a startGestureTransition() on a detached root.',1968 );1969 }1970 // We assume that the clone is still within the same parent.1971 containerParent.removeChild(clone);19721973 // Now the root is on the containerInstance itself until we call restoreRootViewTransitionName.1974 containerInstance.style.viewTransitionName = 'root';1975}19761977export type InstanceMeasurement = {1978 rect: ClientRect | DOMRect,1979 abs: boolean, // is absolutely positioned1980 clip: boolean, // is a clipping parent1981 view: boolean, // is in viewport bounds1982};19831984function createMeasurement(1985 rect: ClientRect | DOMRect,1986 computedStyle: CSSStyleDeclaration,1987 element: Element,1988): InstanceMeasurement {1989 const ownerWindow = element.ownerDocument.defaultView;1990 return {1991 rect: rect,1992 abs:1993 // Absolutely positioned instances don't contribute their size to the parent.1994 computedStyle.position === 'absolute' ||1995 computedStyle.position === 'fixed',1996 clip:1997 // If a ViewTransition boundary acts as a clipping parent group we should1998 // always mark it to animate if its children do so that we can clip them.1999 // This doesn't actually have any effect yet until browsers implement2000 // layered capture and nested view transitions.
Findings
✓ No findings reported for this file.