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 isFiberContainedByFragment,75 isFragmentContainedByFiber,76} from 'react-reconciler/src/ReactFiberTreeReflection';77import {compareDocumentPositionForEmptyFragment} from 'shared/ReactDOMFragmentRefShared';7879export {detachDeletedInstance};80import {hasRole} from './DOMAccessibilityRoles';81import type {SingletonType} from './ReactDOMComponent';82import {83 setInitialProperties,84 updateProperties,85 clearSingletonProperties,86 hydrateProperties,87 hydrateText,88 diffHydratedProperties,89 getPropsFromElement,90 diffHydratedText,91 trapClickOnNonInteractiveElement,92 clearClickListener,93} from './ReactDOMComponent';94import {hydrateInput} from './ReactDOMInput';95import {hydrateTextarea} from './ReactDOMTextarea';96import {hydrateSelect} from './ReactDOMSelect';97import {getSelectionInformation, restoreSelection} from './ReactInputSelection';98import setTextContent from './setTextContent';99import {100 validateDOMNesting,101 validateTextNesting,102 updatedAncestorInfoDev,103} from './validateDOMNesting';104import {105 isEnabled as ReactBrowserEventEmitterIsEnabled,106 setEnabled as ReactBrowserEventEmitterSetEnabled,107} from '../events/ReactDOMEventListener';108import {SVG_NAMESPACE, MATH_NAMESPACE} from './DOMNamespaces';109import {110 ELEMENT_NODE,111 TEXT_NODE,112 COMMENT_NODE,113 DOCUMENT_NODE,114 DOCUMENT_TYPE_NODE,115 DOCUMENT_FRAGMENT_NODE,116} from './HTMLNodeType';117118import {119 flushEventReplaying,120 retryIfBlockedOn,121} from '../events/ReactDOMEventReplaying';122123import {124 enableCreateEventHandleAPI,125 enableScopeAPI,126 enableTrustedTypesIntegration,127 disableLegacyMode,128 enableMoveBefore,129 disableCommentsAsDOMContainers,130 enableSuspenseyImages,131 enableSrcObject,132 enableViewTransition,133 enableHydrationChangeEvent,134 enableFragmentRefsScrollIntoView,135 enableProfilerTimer,136 enableFragmentRefsInstanceHandles,137 enableFragmentRefsTextNodes,138} from 'shared/ReactFeatureFlags';139import {140 HostComponent,141 HostHoistable,142 HostText,143 HostSingleton,144} from 'react-reconciler/src/ReactWorkTags';145import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';146import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';147import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';148import {flushSyncWork as flushSyncWorkOnAllRoots} from 'react-reconciler/src/ReactFiberWorkLoop';149import {requestFormReset as requestFormResetOnFiber} from 'react-reconciler/src/ReactFiberHooks';150151import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';152153export {default as rendererVersion} from 'shared/ReactVersion';154155import noop from 'shared/noop';156import estimateBandwidth from './estimateBandwidth';157158export const rendererPackageName = 'react-dom';159export const extraDevToolsConfig = null;160161export type Type = string;162export type Props = {163 autoFocus?: boolean,164 children?: mixed,165 disabled?: boolean,166 hidden?: boolean,167 suppressHydrationWarning?: boolean,168 dangerouslySetInnerHTML?: mixed,169 style?: {170 display?: string,171 viewTransitionName?: string,172 'view-transition-name'?: string,173 viewTransitionClass?: string,174 'view-transition-class'?: string,175 margin?: string,176 marginTop?: string,177 'margin-top'?: string,178 marginBottom?: string,179 'margin-bottom'?: string,180 ...181 },182 bottom?: null | number,183 left?: null | number,184 right?: null | number,185 top?: null | number,186 is?: string,187 size?: number,188 value?: string,189 defaultValue?: string,190 checked?: boolean,191 defaultChecked?: boolean,192 multiple?: boolean,193 type?: string,194 src?: string | Blob | MediaSource | MediaStream, // TODO: Response195 srcSet?: string,196 loading?: 'eager' | 'lazy',197 onLoad?: (event: any) => void,198 ...199};200type RawProps = {201 [string]: mixed,202};203export type EventTargetChildElement = {204 type: string,205 props: null | {206 style?: {207 position?: string,208 zIndex?: number,209 bottom?: string,210 left?: string,211 right?: string,212 top?: string,213 ...214 },215 ...216 },217 ...218};219220export type Container =221 | interface extends Element {_reactRootContainer?: FiberRoot}222 | interface extends Document {_reactRootContainer?: FiberRoot}223 | interface extends DocumentFragment {_reactRootContainer?: FiberRoot};224export type Instance = Element;225export type TextInstance = Text;226227type InstanceWithFragmentHandles = Instance & {228 reactFragments?: Set<FragmentInstanceType>,229};230type HostNodeWithFragmentHandles = (Instance | TextInstance) & {231 reactFragments?: Set<FragmentInstanceType>,232};233234declare class ActivityInterface extends Comment {}235declare class SuspenseInterface extends Comment {236 _reactRetry: void | (() => void);237}238239export type ActivityInstance = ActivityInterface;240export type SuspenseInstance = SuspenseInterface;241242type FormStateMarkerInstance = Comment;243export type HydratableInstance =244 | Instance245 | TextInstance246 | ActivityInstance247 | SuspenseInstance248 | FormStateMarkerInstance;249export type PublicInstance = Element | Text;250export type HostContextDev = {251 context: HostContextProd,252 ancestorInfo: AncestorInfoDev,253};254type HostContextProd = HostContextNamespace;255export type HostContext = HostContextDev | HostContextProd;256export type UpdatePayload = Array<mixed>;257export type ChildSet = void; // Unused258export type TimeoutHandle = TimeoutID;259export type NoTimeout = -1;260export type RendererInspectionConfig = $ReadOnly<{}>;261262export type TransitionStatus = FormStatus;263264export type ViewTransitionInstance = {265 name: string,266 group: mixin$Animatable,267 imagePair: mixin$Animatable,268 old: mixin$Animatable,269 new: mixin$Animatable,270};271272type SelectionInformation = {273 focusedElem: null | HTMLElement,274 selectionRange: mixed,275};276277const SUPPRESS_HYDRATION_WARNING = 'suppressHydrationWarning';278279const ACTIVITY_START_DATA = '&';280const ACTIVITY_END_DATA = '/&';281const SUSPENSE_START_DATA = '$';282const SUSPENSE_END_DATA = '/$';283const SUSPENSE_PENDING_START_DATA = '$?';284const SUSPENSE_QUEUED_START_DATA = '$~';285const SUSPENSE_FALLBACK_START_DATA = '$!';286const PREAMBLE_CONTRIBUTION_HTML = 'html';287const PREAMBLE_CONTRIBUTION_BODY = 'body';288const PREAMBLE_CONTRIBUTION_HEAD = 'head';289const FORM_STATE_IS_MATCHING = 'F!';290const FORM_STATE_IS_NOT_MATCHING = 'F';291292const DOCUMENT_READY_STATE_LOADING = 'loading';293294const STYLE = 'style';295296opaque type HostContextNamespace = 0 | 1 | 2;297export const HostContextNamespaceNone: HostContextNamespace = 0;298const HostContextNamespaceSvg: HostContextNamespace = 1;299const HostContextNamespaceMath: HostContextNamespace = 2;300301let eventsEnabled: ?boolean = null;302let selectionInformation: null | SelectionInformation = null;303304export * from 'react-reconciler/src/ReactFiberConfigWithNoPersistence';305306function getOwnerDocumentFromRootContainer(307 rootContainerElement: Element | Document | DocumentFragment,308): Document {309 return rootContainerElement.nodeType === DOCUMENT_NODE310 ? (rootContainerElement as any)311 : rootContainerElement.ownerDocument;312}313314export function getRootHostContext(315 rootContainerInstance: Container,316): HostContext {317 let type;318 let context: HostContextProd;319 const nodeType = rootContainerInstance.nodeType;320 switch (nodeType) {321 case DOCUMENT_NODE:322 case DOCUMENT_FRAGMENT_NODE: {323 type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';324 const root = (rootContainerInstance as any).documentElement;325 if (root) {326 const namespaceURI = root.namespaceURI;327 context = namespaceURI328 ? getOwnHostContext(namespaceURI)329 : HostContextNamespaceNone;330 } else {331 context = HostContextNamespaceNone;332 }333 break;334 }335 default: {336 const container: any =337 !disableCommentsAsDOMContainers && nodeType === COMMENT_NODE338 ? rootContainerInstance.parentNode339 : rootContainerInstance;340 type = container.tagName;341 const namespaceURI = container.namespaceURI;342 if (!namespaceURI) {343 switch (type) {344 case 'svg':345 context = HostContextNamespaceSvg;346 break;347 case 'math':348 context = HostContextNamespaceMath;349 break;350 default:351 context = HostContextNamespaceNone;352 break;353 }354 } else {355 const ownContext = getOwnHostContext(namespaceURI);356 context = getChildHostContextProd(ownContext, type);357 }358 break;359 }360 }361 if (__DEV__) {362 const validatedTag = type.toLowerCase();363 const ancestorInfo = updatedAncestorInfoDev(null, validatedTag);364 return {context, ancestorInfo};365 }366 return context;367}368369function getOwnHostContext(namespaceURI: string): HostContextNamespace {370 switch (namespaceURI) {371 case SVG_NAMESPACE:372 return HostContextNamespaceSvg;373 case MATH_NAMESPACE:374 return HostContextNamespaceMath;375 default:376 return HostContextNamespaceNone;377 }378}379380function getChildHostContextProd(381 parentNamespace: HostContextNamespace,382 type: string,383): HostContextNamespace {384 if (parentNamespace === HostContextNamespaceNone) {385 // No (or default) parent namespace: potential entry point.386 switch (type) {387 case 'svg':388 return HostContextNamespaceSvg;389 case 'math':390 return HostContextNamespaceMath;391 default:392 return HostContextNamespaceNone;393 }394 }395 if (parentNamespace === HostContextNamespaceSvg && type === 'foreignObject') {396 // We're leaving SVG.397 return HostContextNamespaceNone;398 }399 // By default, pass namespace below.400 return parentNamespace;401}402403export function getChildHostContext(404 parentHostContext: HostContext,405 type: string,406): HostContext {407 if (__DEV__) {408 const parentHostContextDev = parentHostContext as any as HostContextDev;409 const context = getChildHostContextProd(parentHostContextDev.context, type);410 const ancestorInfo = updatedAncestorInfoDev(411 parentHostContextDev.ancestorInfo,412 type,413 );414 return {context, ancestorInfo};415 }416 const parentNamespace = parentHostContext as any as HostContextProd;417 return getChildHostContextProd(parentNamespace, type);418}419420export function getPublicInstance(instance: Instance): Instance {421 return instance;422}423424export function prepareForCommit(containerInfo: Container): Object | null {425 eventsEnabled = ReactBrowserEventEmitterIsEnabled();426 selectionInformation = getSelectionInformation(containerInfo);427 let activeInstance = null;428 if (enableCreateEventHandleAPI) {429 const focusedElem = selectionInformation.focusedElem;430 if (focusedElem !== null) {431 activeInstance = getClosestInstanceFromNode(focusedElem);432 }433 }434 ReactBrowserEventEmitterSetEnabled(false);435 return activeInstance;436}437438export function beforeActiveInstanceBlur(internalInstanceHandle: Object): void {439 if (enableCreateEventHandleAPI) {440 ReactBrowserEventEmitterSetEnabled(true);441 dispatchBeforeDetachedBlur(442 (selectionInformation as any).focusedElem,443 internalInstanceHandle,444 );445 ReactBrowserEventEmitterSetEnabled(false);446 }447}448449export function afterActiveInstanceBlur(): void {450 if (enableCreateEventHandleAPI) {451 ReactBrowserEventEmitterSetEnabled(true);452 dispatchAfterDetachedBlur((selectionInformation as any).focusedElem);453 ReactBrowserEventEmitterSetEnabled(false);454 }455}456457export function resetAfterCommit(containerInfo: Container): void {458 restoreSelection(selectionInformation, containerInfo);459 ReactBrowserEventEmitterSetEnabled(eventsEnabled);460 eventsEnabled = null;461 selectionInformation = null;462}463464export function createHoistableInstance(465 type: string,466 props: Props,467 rootContainerInstance: Container,468 internalInstanceHandle: Object,469): Instance {470 const ownerDocument = getOwnerDocumentFromRootContainer(471 rootContainerInstance,472 );473474 const domElement: Instance = ownerDocument.createElement(type);475 precacheFiberNode(internalInstanceHandle, domElement);476 updateFiberProps(domElement, props);477 setInitialProperties(domElement, type, props);478 markNodeAsHoistable(domElement);479 return domElement;480}481482let didWarnScriptTags = false;483function isScriptDataBlock(props: Props): boolean {484 const scriptType = props.type;485 if (typeof scriptType !== 'string' || scriptType === '') {486 return false;487 }488 const lower = scriptType.toLowerCase();489 // Special non-MIME keywords recognized by the HTML spec490 // TODO: May be fine to also not warn about having these types be parsed as "parser-inserted"491 if (492 lower === 'module' ||493 lower === 'importmap' ||494 lower === 'speculationrules'495 ) {496 return false;497 }498 // JavaScript MIME types per https://mimesniff.spec.whatwg.org/#javascript-mime-type499 switch (lower) {500 case 'application/ecmascript':501 case 'application/javascript':502 case 'application/x-ecmascript':503 case 'application/x-javascript':504 case 'text/ecmascript':505 case 'text/javascript':506 case 'text/javascript1.0':507 case 'text/javascript1.1':508 case 'text/javascript1.2':509 case 'text/javascript1.3':510 case 'text/javascript1.4':511 case 'text/javascript1.5':512 case 'text/jscript':513 case 'text/livescript':514 case 'text/x-ecmascript':515 case 'text/x-javascript':516 return false;517 }518 // Any other non-empty type value means this is a data block519 return true;520}521const warnedUnknownTags: {522 [key: string]: boolean,523} = {524 // There are working polyfills for <dialog>. Let people use it.525 dialog: true,526 // Electron ships a custom <webview> tag to display external web content in527 // an isolated frame and process.528 // This tag is not present in non Electron environments such as JSDom which529 // is often used for testing purposes.530 // @see https://electronjs.org/docs/api/webview-tag531 webview: true,532};533534export function createInstance(535 type: string,536 props: Props,537 rootContainerInstance: Container,538 hostContext: HostContext,539 internalInstanceHandle: Object,540): Instance {541 let hostContextProd: HostContextProd;542 if (__DEV__) {543 // TODO: take namespace into account when validating.544 const hostContextDev: HostContextDev = hostContext as any;545 validateDOMNesting(type, hostContextDev.ancestorInfo);546 hostContextProd = hostContextDev.context;547 } else {548 hostContextProd = hostContext as any;549 }550551 const ownerDocument = getOwnerDocumentFromRootContainer(552 rootContainerInstance,553 );554555 let domElement: Instance;556 switch (hostContextProd) {557 case HostContextNamespaceSvg:558 domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);559 break;560 case HostContextNamespaceMath:561 domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);562 break;563 default:564 switch (type) {565 case 'svg': {566 domElement = ownerDocument.createElementNS(SVG_NAMESPACE, type);567 break;568 }569 case 'math': {570 domElement = ownerDocument.createElementNS(MATH_NAMESPACE, type);571 break;572 }573 case 'script': {574 // Create the script via .innerHTML so its "parser-inserted" flag is575 // set to true and it does not execute576 const div = ownerDocument.createElement('div');577 if (__DEV__) {578 if (579 enableTrustedTypesIntegration &&580 !didWarnScriptTags &&581 // Data block scripts are not executed by UAs anyway so582 // we don't need to warn: https://html.spec.whatwg.org/multipage/scripting.html#attr-script-type583 !isScriptDataBlock(props)584 ) {585 console.error(586 'Encountered a script tag while rendering React component. ' +587 'Scripts inside React components are never executed when rendering ' +588 'on the client. Consider using template tag instead ' +589 '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).',590 );591 didWarnScriptTags = true;592 }593 }594 div.innerHTML = '<script><' + '/script>';595 // This is guaranteed to yield a script element.596 const firstChild = div.firstChild as any as HTMLScriptElement;597 domElement = div.removeChild(firstChild);598 break;599 }600 case 'select': {601 if (typeof props.is === 'string') {602 domElement = ownerDocument.createElement('select', {is: props.is});603 } else {604 // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.605 // See discussion in https://github.com/facebook/react/pull/6896606 // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240607 domElement = ownerDocument.createElement('select');608 }609 if (props.multiple) {610 domElement.multiple = true;611 } else if (props.size) {612 // Setting a size greater than 1 causes a select to behave like `multiple=true`, where613 // it is possible that no option is selected.614 //615 // This is only necessary when a select in "single selection mode".616 domElement.size = props.size;617 }618 break;619 }620 default: {621 if (typeof props.is === 'string') {622 domElement = ownerDocument.createElement(type, {is: props.is});623 } else {624 // Separate else branch instead of using `props.is || undefined` above because of a Firefox bug.625 // See discussion in https://github.com/facebook/react/pull/6896626 // and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240627 domElement = ownerDocument.createElement(type);628 }629630 if (__DEV__) {631 if (type.indexOf('-') === -1) {632 // We're not SVG/MathML and we don't have a dash, so we're not a custom element633 // Even if you use `is`, these should be of known type and lower case.634 if (type !== type.toLowerCase()) {635 console.error(636 '<%s /> is using incorrect casing. ' +637 'Use PascalCase for React components, ' +638 'or lowercase for HTML elements.',639 type,640 );641 }642 if (643 // $FlowFixMe[method-unbinding]644 Object.prototype.toString.call(domElement) ===645 '[object HTMLUnknownElement]' &&646 !hasOwnProperty.call(warnedUnknownTags, type)647 ) {648 warnedUnknownTags[type] = true;649 console.error(650 'The tag <%s> is unrecognized in this browser. ' +651 'If you meant to render a React component, start its name with ' +652 'an uppercase letter.',653 type,654 );655 }656 }657 }658 }659 }660 }661 precacheFiberNode(internalInstanceHandle, domElement);662 updateFiberProps(domElement, props);663 return domElement;664}665666let didWarnForClone = false;667668export function cloneMutableInstance(669 instance: Instance,670 keepChildren: boolean,671): Instance {672 if (__DEV__) {673 // Warn for problematic674 const tagName = instance.tagName;675 switch (tagName) {676 case 'VIDEO':677 case 'IFRAME':678 if (!didWarnForClone) {679 didWarnForClone = true;680 // TODO: Once we have the ability to avoid cloning the root, suggest an absolutely681 // positioned ViewTransition instead as the solution.682 console.warn(683 'startGestureTransition() required cloning a <%s> element since it exists in ' +684 'both states of the gesture. This can be problematic since it will load it twice ' +685 'Try removing or hiding it with <Activity mode="offscreen"> in the optimistic state.',686 tagName.toLowerCase(),687 );688 }689 break;690 }691 }692 return instance.cloneNode(keepChildren);693}694695export function appendInitialChild(696 parentInstance: Instance,697 child: Instance | TextInstance,698): void {699 // Note: This should not use moveBefore() because initial are appended while disconnected.700 parentInstance.appendChild(child);701}702703export function finalizeInitialChildren(704 domElement: Instance,705 type: string,706 props: Props,707 hostContext: HostContext,708): boolean {709 setInitialProperties(domElement, type, props);710 switch (type) {711 case 'button':712 case 'input':713 case 'select':714 case 'textarea':715 return !!props.autoFocus;716 case 'img':717 return true;718 default:719 return false;720 }721}722723export function finalizeHydratedChildren(724 domElement: Instance,725 type: string,726 props: Props,727 hostContext: HostContext,728): boolean {729 // TOOD: Consider unifying this with hydrateInstance.730 if (!enableHydrationChangeEvent) {731 return false;732 }733 switch (type) {734 case 'input':735 case 'select':736 case 'textarea':737 case 'img':738 return true;739 default:740 return false;741 }742}743744export function shouldSetTextContent(type: string, props: Props): boolean {745 return (746 type === 'textarea' ||747 type === 'noscript' ||748 typeof props.children === 'string' ||749 typeof props.children === 'number' ||750 typeof props.children === 'bigint' ||751 (typeof props.dangerouslySetInnerHTML === 'object' &&752 props.dangerouslySetInnerHTML !== null &&753 props.dangerouslySetInnerHTML.__html != null)754 );755}756757export function createTextInstance(758 text: string,759 rootContainerInstance: Container,760 hostContext: HostContext,761 internalInstanceHandle: Object,762): TextInstance {763 if (__DEV__) {764 const hostContextDev = hostContext as any as HostContextDev;765 const ancestor = hostContextDev.ancestorInfo.current;766 if (ancestor != null) {767 validateTextNesting(768 text,769 ancestor.tag,770 hostContextDev.ancestorInfo.implicitRootScope,771 );772 }773 }774 const textNode: TextInstance = getOwnerDocumentFromRootContainer(775 rootContainerInstance,776 ).createTextNode(text);777 precacheFiberNode(internalInstanceHandle, textNode);778 return textNode;779}780781export function cloneMutableTextInstance(782 textInstance: TextInstance,783): TextInstance {784 return textInstance.cloneNode(false);785}786787let currentPopstateTransitionEvent: Event | null = null;788export function shouldAttemptEagerTransition(): boolean {789 const event = window.event;790 if (event && event.type === 'popstate') {791 // This is a popstate event. Attempt to render any transition during this792 // event synchronously. Unless we already attempted during this event.793 if (event === currentPopstateTransitionEvent) {794 // We already attempted to render this popstate transition synchronously.795 // Any subsequent attempts must have happened as the result of a derived796 // update, like startTransition inside useEffect, or useDV. Switch back to797 // the default behavior for all remaining transitions during the current798 // popstate event.799 return false;800 } else {801 // Cache the current event in case a derived transition is scheduled.802 // (Refer to previous branch.)803 currentPopstateTransitionEvent = event;804 return true;805 }806 }807 // We're not inside a popstate event.808 currentPopstateTransitionEvent = null;809 return false;810}811812let schedulerEvent: void | Event = undefined;813export function trackSchedulerEvent(): void {814 schedulerEvent = window.event;815}816817export function resolveEventType(): null | string {818 const event = window.event;819 return event && event !== schedulerEvent ? event.type : null;820}821822export function resolveEventTimeStamp(): number {823 const event = window.event;824 return event && event !== schedulerEvent ? event.timeStamp : -1.1;825}826827export const isPrimaryRenderer = true;828export const warnsIfNotActing = true;829// This initialization code may run even on server environments830// if a component just imports ReactDOM (e.g. for findDOMNode).831// Some environments might not have setTimeout or clearTimeout.832export const scheduleTimeout: any =833 typeof setTimeout === 'function' ? setTimeout : (undefined as any);834export const cancelTimeout: any =835 typeof clearTimeout === 'function' ? clearTimeout : (undefined as any);836export const noTimeout: -1 = -1;837const localPromise = typeof Promise === 'function' ? Promise : undefined;838const localRequestAnimationFrame =839 typeof requestAnimationFrame === 'function'840 ? requestAnimationFrame841 : scheduleTimeout;842843export {getClosestInstanceFromNode as getInstanceFromNode};844845export function preparePortalMount(portalInstance: Instance): void {846 listenToAllSupportedEvents(portalInstance);847}848849export function prepareScopeUpdate(850 scopeInstance: ReactScopeInstance,851 internalInstanceHandle: Object,852): void {853 if (enableScopeAPI) {854 precacheFiberNode(internalInstanceHandle, scopeInstance);855 }856}857858export function getInstanceFromScope(859 scopeInstance: ReactScopeInstance,860): null | Object {861 if (enableScopeAPI) {862 return getFiberFromScopeInstance(scopeInstance);863 }864 return null;865}866867// -------------------868// Microtasks869// -------------------870export const supportsMicrotasks = true;871export const scheduleMicrotask: any =872 typeof queueMicrotask === 'function'873 ? queueMicrotask874 : typeof localPromise !== 'undefined'875 ? callback =>876 localPromise.resolve(null).then(callback).catch(handleErrorInNextTick)877 : scheduleTimeout; // TODO: Determine the best fallback here.878879function handleErrorInNextTick(error: any) {880 setTimeout(() => {881 throw error;882 });883}884885// -------------------886// Mutation887// -------------------888889export const supportsMutation = true;890891export function commitMount(892 domElement: Instance,893 type: string,894 newProps: Props,895 internalInstanceHandle: Object,896): void {897 // Despite the naming that might imply otherwise, this method only898 // fires if there is an `Update` effect scheduled during mounting.899 // This happens if `finalizeInitialChildren` returns `true` (which it900 // does to implement the `autoFocus` attribute on the client). But901 // there are also other cases when this might happen (such as patching902 // up text content during hydration mismatch). So we'll check this again.903 switch (type) {904 case 'button':905 case 'input':906 case 'select':907 case 'textarea':908 if (newProps.autoFocus) {909 (910 domElement as any as911 | HTMLButtonElement912 | HTMLInputElement913 | HTMLSelectElement914 | HTMLTextAreaElement915 ).focus();916 }917 return;918 case 'img': {919 // The technique here is to assign the src or srcSet property to cause the browser920 // to issue a new load event. If it hasn't loaded yet it'll fire whenever the load actually completes.921 // If it has already loaded we missed it so the second load will still be the first one that executes922 // any associated onLoad props.923 // Even if we have srcSet we prefer to reassign src. The reason is that Firefox does not trigger a new924 // load event when only srcSet is assigned. Chrome will trigger a load event if either is assigned so we925 // only need to assign one. And Safari just never triggers a new load event which means this technique926 // is already a noop regardless of which properties are assigned. We should revisit if browsers update927 // this heuristic in the future.928 if (newProps.src) {929 const src = (newProps as any).src;930 if (enableSrcObject && typeof src === 'object') {931 // For object src, we can't just set the src again to the same blob URL because it might have932 // already revoked if it loaded before this. However, we can create a new blob URL and set that.933 // This is relatively cheap since the blob is already in memory but this might cause some934 // duplicated work.935 // TODO: We could maybe detect if load hasn't fired yet and if so reuse the URL.936 try {937 setSrcObject(domElement, type, src);938 return;939 } catch (x) {940 // If URL.createObjectURL() errors, it was probably some other object type941 // that should be toString:ed instead, so we just fall-through to the normal942 // path.943 }944 }945 (domElement as any as HTMLImageElement).src = src;946 } else if (newProps.srcSet) {947 (domElement as any as HTMLImageElement).srcset = (948 newProps as any949 ).srcSet;950 }951 return;952 }953 }954}955956export function commitHydratedInstance(957 domElement: Instance,958 type: string,959 props: Props,960 internalInstanceHandle: Object,961): void {962 if (!enableHydrationChangeEvent) {963 return;964 }965 // This fires in the commit phase if a hydrated instance needs to do further966 // work in the commit phase. Similar to commitMount. However, this should not967 // do things that would've already happened such as set auto focus since that968 // would steal focus. It's only scheduled if finalizeHydratedChildren returns969 // true.970 switch (type) {971 case 'input': {972 hydrateInput(973 domElement,974 props.value,975 props.defaultValue,976 props.checked,977 props.defaultChecked,978 );979 break;980 }981 case 'select': {982 hydrateSelect(983 domElement,984 props.value,985 props.defaultValue,986 props.multiple,987 );988 break;989 }990 case 'textarea':991 hydrateTextarea(domElement, props.value, props.defaultValue);992 break;993 case 'img':994 // TODO: Should we replay onLoad events?995 break;996 }997}998999export function commitUpdate(1000 domElement: Instance,1001 type: string,1002 oldProps: Props,1003 newProps: Props,1004 internalInstanceHandle: Object,1005): void {1006 // Diff and update the properties.1007 updateProperties(domElement, type, oldProps, newProps);10081009 // Update the props handle so that we know which props are the ones with1010 // with current event handlers.1011 updateFiberProps(domElement, newProps);1012}10131014export function resetTextContent(domElement: Instance): void {1015 setTextContent(domElement, '');1016}10171018export function commitTextUpdate(1019 textInstance: TextInstance,1020 oldText: string,1021 newText: string,1022): void {1023 textInstance.nodeValue = newText;1024}10251026const supportsMoveBefore =1027 // $FlowFixMe[prop-missing]: We're doing the feature detection here.1028 enableMoveBefore &&1029 typeof window !== 'undefined' &&1030 typeof window.Element.prototype.moveBefore === 'function';10311032export function appendChild(1033 parentInstance: Instance,1034 child: Instance | TextInstance,1035): void {1036 if (supportsMoveBefore && child.parentNode !== null) {1037 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1038 parentInstance.moveBefore(child, null);1039 } else {1040 parentInstance.appendChild(child);1041 }1042}10431044function warnForReactChildrenConflict(container: Container): void {1045 if (__DEV__) {1046 if ((container as any).__reactWarnedAboutChildrenConflict) {1047 return;1048 }1049 const props = getFiberCurrentPropsFromNode(container);1050 if (props !== null) {1051 const fiber = getInstanceFromNode(container);1052 if (fiber !== null) {1053 if (1054 typeof props.children === 'string' ||1055 typeof props.children === 'number'1056 ) {1057 (container as any).__reactWarnedAboutChildrenConflict = true;1058 // Run the warning with the Fiber of the container for context of where the children are specified.1059 // We could also maybe use the Portal. The current execution context is the child being added.1060 runWithFiberInDEV(fiber, () => {1061 console.error(1062 'Cannot use a ref on a React element as a container to `createRoot` or `createPortal` ' +1063 'if that element also sets "children" text content using React. It should be a leaf with no children. ' +1064 "Otherwise it's ambiguous which children should be used.",1065 );1066 });1067 } else if (props.dangerouslySetInnerHTML != null) {1068 (container as any).__reactWarnedAboutChildrenConflict = true;1069 runWithFiberInDEV(fiber, () => {1070 console.error(1071 'Cannot use a ref on a React element as a container to `createRoot` or `createPortal` ' +1072 'if that element also sets "dangerouslySetInnerHTML" using React. It should be a leaf with no children. ' +1073 "Otherwise it's ambiguous which children should be used.",1074 );1075 });1076 }1077 }1078 }1079 }1080}10811082export function appendChildToContainer(1083 container: Container,1084 child: Instance | TextInstance,1085): void {1086 if (__DEV__) {1087 warnForReactChildrenConflict(container);1088 }1089 let parentNode: DocumentFragment | Element;1090 if (container.nodeType === DOCUMENT_NODE) {1091 parentNode = (container as any).body;1092 } else if (1093 !disableCommentsAsDOMContainers &&1094 container.nodeType === COMMENT_NODE1095 ) {1096 parentNode = container.parentNode as any;1097 if (supportsMoveBefore && child.parentNode !== null) {1098 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1099 parentNode.moveBefore(child, container);1100 } else {1101 parentNode.insertBefore(child, container);1102 }1103 return;1104 } else if (container.nodeName === 'HTML') {1105 parentNode = container.ownerDocument.body as any;1106 } else {1107 parentNode = container as any;1108 }1109 if (supportsMoveBefore && child.parentNode !== null) {1110 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1111 parentNode.moveBefore(child, null);1112 } else {1113 parentNode.appendChild(child);1114 }11151116 // This container might be used for a portal.1117 // If something inside a portal is clicked, that click should bubble1118 // through the React tree. However, on Mobile Safari the click would1119 // never bubble through the *DOM* tree unless an ancestor with onclick1120 // event exists. So we wouldn't see it and dispatch it.1121 // This is why we ensure that non React root containers have inline onclick1122 // defined.1123 // https://github.com/facebook/react/issues/119181124 const reactRootContainer = container._reactRootContainer;1125 if (1126 // $FlowFixMe[invalid-compare]1127 (reactRootContainer === null || reactRootContainer === undefined) &&1128 parentNode.onclick === null1129 ) {1130 // TODO: This cast may not be sound for SVG, MathML or custom elements.1131 trapClickOnNonInteractiveElement(parentNode as any as HTMLElement);1132 }1133}11341135export function insertBefore(1136 parentInstance: Instance,1137 child: Instance | TextInstance,1138 beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,1139): void {1140 if (supportsMoveBefore && child.parentNode !== null) {1141 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1142 parentInstance.moveBefore(child, beforeChild);1143 } else {1144 parentInstance.insertBefore(child, beforeChild);1145 }1146}11471148export function insertInContainerBefore(1149 container: Container,1150 child: Instance | TextInstance,1151 beforeChild: Instance | TextInstance | SuspenseInstance | ActivityInstance,1152): void {1153 if (__DEV__) {1154 warnForReactChildrenConflict(container);1155 }1156 let parentNode: DocumentFragment | Element;1157 if (container.nodeType === DOCUMENT_NODE) {1158 parentNode = (container as any).body;1159 } else if (1160 !disableCommentsAsDOMContainers &&1161 container.nodeType === COMMENT_NODE1162 ) {1163 parentNode = container.parentNode as any;1164 } else if (container.nodeName === 'HTML') {1165 parentNode = container.ownerDocument.body as any;1166 } else {1167 parentNode = container as any;1168 }1169 if (supportsMoveBefore && child.parentNode !== null) {1170 // $FlowFixMe[prop-missing]: We've checked this with supportsMoveBefore.1171 parentNode.moveBefore(child, beforeChild);1172 } else {1173 parentNode.insertBefore(child, beforeChild);1174 }1175}11761177export function isSingletonScope(type: string): boolean {1178 return type === 'head';1179}11801181function createEvent(type: DOMEventName, bubbles: boolean): Event {1182 const event = document.createEvent('Event');1183 event.initEvent(type as any as string, bubbles, false);1184 return event;1185}11861187function dispatchBeforeDetachedBlur(1188 target: HTMLElement,1189 internalInstanceHandle: Object,1190): void {1191 if (enableCreateEventHandleAPI) {1192 const event = createEvent('beforeblur', true);1193 // Dispatch "beforeblur" directly on the target,1194 // so it gets picked up by the event system and1195 // can propagate through the React internal tree.1196 // $FlowFixMe[prop-missing]: internal field1197 event._detachedInterceptFiber = internalInstanceHandle;1198 target.dispatchEvent(event);1199 }1200}12011202function dispatchAfterDetachedBlur(target: HTMLElement): void {1203 if (enableCreateEventHandleAPI) {1204 const event = createEvent('afterblur', false);1205 // So we know what was detached, make the relatedTarget the1206 // detached target on the "afterblur" event.1207 (event as any).relatedTarget = target;1208 // Dispatch the event on the document.1209 document.dispatchEvent(event);1210 }1211}12121213export function removeChild(1214 parentInstance: Instance,1215 child: Instance | TextInstance | SuspenseInstance | ActivityInstance,1216): void {1217 parentInstance.removeChild(child);1218}12191220export function removeChildFromContainer(1221 container: Container,1222 child: Instance | TextInstance | SuspenseInstance | ActivityInstance,1223): void {1224 let parentNode: DocumentFragment | Element;1225 if (container.nodeType === DOCUMENT_NODE) {1226 parentNode = (container as any).body;1227 } else if (1228 !disableCommentsAsDOMContainers &&1229 container.nodeType === COMMENT_NODE1230 ) {1231 parentNode = container.parentNode as any;1232 } else if (container.nodeName === 'HTML') {1233 parentNode = container.ownerDocument.body as any;1234 } else {1235 parentNode = container as any;1236 }1237 parentNode.removeChild(child);1238}12391240function clearHydrationBoundary(1241 parentInstance: Instance,1242 hydrationInstance: SuspenseInstance | ActivityInstance,1243): void {1244 let node: Node = hydrationInstance;1245 // Delete all nodes within this suspense boundary.1246 // There might be nested nodes so we need to keep track of how1247 // deep we are and only break out when we're back on top.1248 let depth = 0;1249 do {1250 const nextNode = node.nextSibling;1251 parentInstance.removeChild(node);1252 if (nextNode && nextNode.nodeType === COMMENT_NODE) {1253 const data = (nextNode as any).data as string;1254 if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {1255 if (depth === 0) {1256 parentInstance.removeChild(nextNode);1257 // Retry if any event replaying was blocked on this.1258 retryIfBlockedOn(hydrationInstance);1259 return;1260 } else {1261 depth--;1262 }1263 } else if (1264 data === SUSPENSE_START_DATA ||1265 data === SUSPENSE_PENDING_START_DATA ||1266 data === SUSPENSE_QUEUED_START_DATA ||1267 data === SUSPENSE_FALLBACK_START_DATA ||1268 data === ACTIVITY_START_DATA1269 ) {1270 depth++;1271 } else if (data === PREAMBLE_CONTRIBUTION_HTML) {1272 // If a preamble contribution marker is found within the bounds of this boundary,1273 // then it contributed to the html tag and we need to reset it.1274 const ownerDocument = parentInstance.ownerDocument;1275 const documentElement: Element = ownerDocument.documentElement as any;1276 clearSingletonPreambleContribution(documentElement);1277 } else if (data === PREAMBLE_CONTRIBUTION_HEAD) {1278 const ownerDocument = parentInstance.ownerDocument;1279 const head: Element = ownerDocument.head as any;1280 clearSingletonPreambleContribution(head);1281 // We need to clear the head because this is the only singleton that can have children that1282 // were part of this boundary but are not inside this boundary.1283 clearHead(head);1284 } else if (data === PREAMBLE_CONTRIBUTION_BODY) {1285 const ownerDocument = parentInstance.ownerDocument;1286 const body: Element = ownerDocument.body as any;1287 clearSingletonPreambleContribution(body);1288 }1289 }1290 // $FlowFixMe[incompatible-type] we bail out when we get a null1291 node = nextNode;1292 } while (node);1293 // TODO: Warn, we didn't find the end comment boundary.1294 // Retry if any event replaying was blocked on this.1295 retryIfBlockedOn(hydrationInstance);1296}12971298export function clearActivityBoundary(1299 parentInstance: Instance,1300 activityInstance: ActivityInstance,1301): void {1302 clearHydrationBoundary(parentInstance, activityInstance);1303}13041305export function clearSuspenseBoundary(1306 parentInstance: Instance,1307 suspenseInstance: SuspenseInstance,1308): void {1309 clearHydrationBoundary(parentInstance, suspenseInstance);1310}13111312function clearHydrationBoundaryFromContainer(1313 container: Container,1314 hydrationInstance: SuspenseInstance | ActivityInstance,1315): void {1316 let parentNode: DocumentFragment | Element;1317 if (container.nodeType === DOCUMENT_NODE) {1318 parentNode = (container as any).body;1319 } else if (1320 !disableCommentsAsDOMContainers &&1321 container.nodeType === COMMENT_NODE1322 ) {1323 parentNode = container.parentNode as any;1324 } else if (container.nodeName === 'HTML') {1325 parentNode = container.ownerDocument.body as any;1326 } else {1327 parentNode = container as any;1328 }1329 clearHydrationBoundary(parentNode, hydrationInstance);1330 // Retry if any event replaying was blocked on this.1331 retryIfBlockedOn(container);1332}13331334export function clearActivityBoundaryFromContainer(1335 container: Container,1336 activityInstance: ActivityInstance,1337): void {1338 clearHydrationBoundaryFromContainer(container, activityInstance);1339}13401341export function clearSuspenseBoundaryFromContainer(1342 container: Container,1343 suspenseInstance: SuspenseInstance,1344): void {1345 clearHydrationBoundaryFromContainer(container, suspenseInstance);1346}13471348function hideOrUnhideDehydratedBoundary(1349 suspenseInstance: SuspenseInstance | ActivityInstance,1350 isHidden: boolean,1351) {1352 let node: Node = suspenseInstance;1353 // Unhide all nodes within this suspense boundary.1354 let depth = 0;1355 do {1356 const nextNode = node.nextSibling;1357 if (node.nodeType === ELEMENT_NODE) {1358 const instance = node as any as HTMLElement & {_stashedDisplay?: string};1359 if (isHidden) {1360 instance._stashedDisplay = instance.style.display;1361 instance.style.display = 'none';1362 } else {1363 instance.style.display = instance._stashedDisplay || '';1364 if (instance.getAttribute('style') === '') {1365 instance.removeAttribute('style');1366 }1367 }1368 } else if (node.nodeType === TEXT_NODE) {1369 const textNode = node as any as Text & {_stashedText?: string};1370 if (isHidden) {1371 textNode._stashedText = textNode.nodeValue;1372 textNode.nodeValue = '';1373 } else {1374 textNode.nodeValue = textNode._stashedText || '';1375 }1376 }1377 if (nextNode && nextNode.nodeType === COMMENT_NODE) {1378 const data = (nextNode as any).data as string;1379 if (data === SUSPENSE_END_DATA) {1380 if (depth === 0) {1381 return;1382 } else {1383 depth--;1384 }1385 } else if (1386 data === SUSPENSE_START_DATA ||1387 data === SUSPENSE_PENDING_START_DATA ||1388 data === SUSPENSE_QUEUED_START_DATA ||1389 data === SUSPENSE_FALLBACK_START_DATA1390 ) {1391 depth++;1392 }1393 // TODO: Should we hide preamble contribution in this case?1394 }1395 // $FlowFixMe[incompatible-type] we bail out when we get a null1396 node = nextNode;1397 } while (node);1398}13991400export function hideDehydratedBoundary(1401 suspenseInstance: SuspenseInstance,1402): void {1403 hideOrUnhideDehydratedBoundary(suspenseInstance, true);1404}14051406export function hideInstance(instance: Instance): void {1407 // TODO: Does this work for all element types? What about MathML? Should we1408 // pass host context to this method?1409 instance = instance as any as HTMLElement;1410 const style = instance.style;1411 // $FlowFixMe[method-unbinding]1412 if (typeof style.setProperty === 'function') {1413 style.setProperty('display', 'none', 'important');1414 } else {1415 style.display = 'none';1416 }1417}14181419export function hideTextInstance(textInstance: TextInstance): void {1420 textInstance.nodeValue = '';1421}14221423export function unhideDehydratedBoundary(1424 dehydratedInstance: SuspenseInstance | ActivityInstance,1425): void {1426 hideOrUnhideDehydratedBoundary(dehydratedInstance, false);1427}14281429export function unhideInstance(instance: Instance, props: Props): void {1430 instance = instance as any as HTMLElement;1431 const styleProp = props[STYLE];1432 const display =1433 styleProp !== undefined &&1434 // $FlowFixMe[invalid-compare]1435 styleProp !== null &&1436 styleProp.hasOwnProperty('display')1437 ? styleProp.display1438 : null;1439 instance.style.display =1440 display == null || typeof display === 'boolean'1441 ? ''1442 : // The value would've errored already if it wasn't safe.1443 // eslint-disable-next-line react-internal/safe-string-coercion1444 ('' + display).trim();1445}14461447export function unhideTextInstance(1448 textInstance: TextInstance,1449 text: string,1450): void {1451 textInstance.nodeValue = text;1452}14531454function warnForBlockInsideInline(instance: HTMLElement) {1455 if (__DEV__) {1456 let nextNode = instance.firstChild;1457 outer: while (nextNode != null) {1458 let node: Node = nextNode;1459 if (1460 node.nodeType === ELEMENT_NODE &&1461 getComputedStyle(node as any).display === 'block'1462 ) {1463 const fiber =1464 getInstanceFromNode(node) || getInstanceFromNode(instance);1465 runWithFiberInDEV(1466 fiber,1467 (parentTag: string, childTag: string) => {1468 console.error(1469 "You're about to start a <ViewTransition> around a display: inline " +1470 'element <%s>, which itself has a display: block element <%s> inside it. ' +1471 'This might trigger a bug in Safari which causes the View Transition to ' +1472 'be skipped with a duplicate name error.\n' +1473 'https://bugs.webkit.org/show_bug.cgi?id=290923',1474 parentTag.toLocaleLowerCase(),1475 childTag.toLocaleLowerCase(),1476 );1477 },1478 instance.tagName,1479 (node as any).tagName,1480 );1481 break;1482 }1483 if (node.firstChild != null) {1484 nextNode = node.firstChild;1485 continue;1486 }1487 if (node === instance) {1488 break;1489 }1490 while (node.nextSibling == null) {1491 if (node.parentNode == null || node.parentNode === instance) {1492 break;1493 }1494 node = node.parentNode;1495 }1496 nextNode = node.nextSibling;1497 }1498 }1499}15001501function countClientRects(rects: Array<ClientRect>): number {1502 if (rects.length === 1) {1503 return 1;1504 }1505 // Count non-zero rects.1506 let count = 0;1507 for (let i = 0; i < rects.length; i++) {1508 const rect = rects[i];1509 if (rect.width > 0 && rect.height > 0) {1510 count++;1511 }1512 }1513 return count;1514}15151516export function applyViewTransitionName(1517 instance: Instance,1518 name: string,1519 className: ?string,1520): void {1521 instance = instance as any as HTMLElement;1522 // If the name isn't valid CSS identifier, base64 encode the name instead.1523 // This doesn't let you select it in custom CSS selectors but it does work in current1524 // browsers.1525 const escapedName =1526 CSS.escape(name) !== name ? 'r-' + btoa(name).replace(/=/g, '') : name;1527 // $FlowFixMe[prop-missing]1528 instance.style.viewTransitionName = escapedName;1529 if (className != null) {1530 // $FlowFixMe[prop-missing]1531 instance.style.viewTransitionClass = className;1532 }1533 const computedStyle = getComputedStyle(instance);1534 if (computedStyle.display === 'inline') {1535 // WebKit has a bug where assigning a name to display: inline elements errors1536 // if they have display: block children. We try to work around this bug in the1537 // simple case by converting it automatically to display: inline-block.1538 // https://bugs.webkit.org/show_bug.cgi?id=2909231539 const rects = instance.getClientRects();1540 if (1541 // $FlowFixMe[incompatible-type]1542 countClientRects(rects) === 11543 ) {1544 // If the instance has a single client rect, that means that it can be1545 // expressed as a display: inline-block or block.1546 // This will cause layout thrash but we live with it since inline view transitions1547 // are unusual.1548 const style = instance.style;1549 // If there's literally only one rect, then it's likely on a single line like an1550 // inline-block. If it's multiple rects but all but one of them are empty it's1551 // likely because it's a single block that caused a line break.1552 style.display = rects.length === 1 ? 'inline-block' : 'block';1553 // Margin doesn't apply to inline so should be zero. However, padding top/bottom1554 // applies to inline-block positioning which we can offset by setting the margin1555 // to the negative padding to get it back into original position.1556 style.marginTop = '-' + computedStyle.paddingTop;1557 style.marginBottom = '-' + computedStyle.paddingBottom;1558 } else {1559 // This case cannot be easily fixed if it has blocks but it's also fine if1560 // it doesn't have blocks. So we only warn in DEV about this being an issue.1561 warnForBlockInsideInline(instance);1562 }1563 }1564}15651566export function restoreViewTransitionName(1567 instance: Instance,1568 props: Props,1569): void {1570 instance = instance as any as HTMLElement;1571 const style = instance.style;1572 const styleProp = props[STYLE];1573 const viewTransitionName =1574 styleProp != null1575 ? styleProp.hasOwnProperty('viewTransitionName')1576 ? styleProp.viewTransitionName1577 : styleProp.hasOwnProperty('view-transition-name')1578 ? styleProp['view-transition-name']1579 : null1580 : null;1581 // $FlowFixMe[prop-missing]1582 style.viewTransitionName =1583 viewTransitionName == null || typeof viewTransitionName === 'boolean'1584 ? ''1585 : // The value would've errored already if it wasn't safe.1586 // eslint-disable-next-line react-internal/safe-string-coercion1587 ('' + viewTransitionName).trim();1588 const viewTransitionClass =1589 styleProp != null1590 ? styleProp.hasOwnProperty('viewTransitionClass')1591 ? styleProp.viewTransitionClass1592 : styleProp.hasOwnProperty('view-transition-class')1593 ? styleProp['view-transition-class']1594 : null1595 : null;1596 // $FlowFixMe[prop-missing]1597 style.viewTransitionClass =1598 viewTransitionClass == null || typeof viewTransitionClass === 'boolean'1599 ? ''1600 : // The value would've errored already if it wasn't safe.1601 // eslint-disable-next-line react-internal/safe-string-coercion1602 ('' + viewTransitionClass).trim();1603 if (style.display === 'inline-block') {1604 // We might have overridden the style. Reset it to what it should be.1605 if (styleProp == null) {1606 style.display = style.margin = '';1607 } else {1608 const display = styleProp.display;1609 style.display =1610 display == null || typeof display === 'boolean' ? '' : display;1611 const margin = styleProp.margin;1612 if (margin != null) {1613 style.margin = margin;1614 } else {1615 const marginTop = styleProp.hasOwnProperty('marginTop')1616 ? styleProp.marginTop1617 : styleProp['margin-top'];1618 style.marginTop =1619 marginTop == null || typeof marginTop === 'boolean' ? '' : marginTop;1620 const marginBottom = styleProp.hasOwnProperty('marginBottom')1621 ? styleProp.marginBottom1622 : styleProp['margin-bottom'];1623 style.marginBottom =1624 marginBottom == null || typeof marginBottom === 'boolean'1625 ? ''1626 : marginBottom;1627 }1628 }1629 }1630}16311632export function cancelViewTransitionName(1633 instance: Instance,1634 oldName: string,1635 props: Props,1636): void {1637 // To cancel the "new" state and paint this instance as part of the parent, all we have to do1638 // is remove the view-transition-name before we exit startViewTransition.1639 restoreViewTransitionName(instance, props);1640 // There isn't a way to cancel an "old" state but what we can do is hide it by animating it.1641 // Since it is already removed from the old state of the parent, this technique only works1642 // if the parent also isn't transitioning. Therefore we should only cancel the root most1643 // ViewTransitions.1644 const documentElement = instance.ownerDocument.documentElement;1645 if (documentElement !== null) {1646 documentElement.animate(1647 {opacity: [0, 0], pointerEvents: ['none', 'none']},1648 // $FlowFixMe[incompatible-type]1649 {1650 duration: 0,1651 fill: 'forwards',1652 pseudoElement: '::view-transition-group(' + oldName + ')',1653 },1654 );1655 }1656}16571658export function cancelRootViewTransitionName(rootContainer: Container): void {1659 const documentElement: null | HTMLElement =1660 rootContainer.nodeType === DOCUMENT_NODE1661 ? (rootContainer as any).documentElement1662 : rootContainer.ownerDocument.documentElement;16631664 if (1665 !disableCommentsAsDOMContainers &&1666 rootContainer.nodeType === COMMENT_NODE1667 ) {1668 if (__DEV__) {1669 console.warn(1670 'Cannot cancel root view transition on a comment node. All view transitions will be globally scoped.',1671 );1672 }1673 return;1674 }16751676 if (1677 documentElement !== null &&1678 // $FlowFixMe[prop-missing]1679 documentElement.style.viewTransitionName === ''1680 ) {1681 // $FlowFixMe[prop-missing]1682 documentElement.style.viewTransitionName = 'none';1683 documentElement.animate(1684 {opacity: [0, 0], pointerEvents: ['none', 'none']},1685 // $FlowFixMe[incompatible-type]1686 {1687 duration: 0,1688 fill: 'forwards',1689 pseudoElement: '::view-transition-group(root)',1690 },1691 );1692 // By default the root ::view-transition selector captures all pointer events,1693 // which means nothing gets interactive. We want to let whatever is not animating1694 // remain interactive during the transition. To do that, we set the size to nothing1695 // so that the transition doesn't capture any clicks. We don't set pointer-events1696 // on this one as that would apply to all running transitions. This lets animations1697 // that are running to block clicks so that they don't end up incorrectly hitting1698 // whatever is below the animation.1699 documentElement.animate(1700 {width: [0, 0], height: [0, 0]},1701 // $FlowFixMe[incompatible-call]1702 // $FlowFixMe[incompatible-type]1703 {1704 duration: 0,1705 fill: 'forwards',1706 pseudoElement: '::view-transition',1707 },1708 );1709 }1710}17111712export function restoreRootViewTransitionName(rootContainer: Container): void {1713 let containerInstance: Instance;1714 if (rootContainer.nodeType === DOCUMENT_NODE) {1715 containerInstance = (rootContainer as any).body;1716 } else if (rootContainer.nodeName === 'HTML') {1717 containerInstance = rootContainer.ownerDocument.body as any;1718 } else {1719 // If the container is not the whole document, then we ideally should probably1720 // clone the whole document outside of the React too.1721 containerInstance = rootContainer as any;1722 }1723 if (1724 !disableCommentsAsDOMContainers &&1725 containerInstance.nodeType === COMMENT_NODE1726 ) {1727 return;1728 }1729 if (1730 // $FlowFixMe[prop-missing]1731 containerInstance.style.viewTransitionName === 'root'1732 ) {1733 // If we moved the root view transition name to the container in a gesture1734 // we need to restore it now.1735 containerInstance.style.viewTransitionName = '';1736 }1737 const documentElement: null | HTMLElement =1738 containerInstance.ownerDocument.documentElement;1739 if (1740 documentElement !== null &&1741 // $FlowFixMe[prop-missing]1742 documentElement.style.viewTransitionName === 'none'1743 ) {1744 // $FlowFixMe[prop-missing]1745 documentElement.style.viewTransitionName = '';1746 }1747}17481749function getComputedTransform(style: CSSStyleDeclaration): string {1750 // Gets the merged transform of all the short hands.1751 const computedStyle: any = style;1752 let transform: string = computedStyle.transform;1753 if (transform === 'none') {1754 transform = '';1755 }1756 const scale: string = computedStyle.scale;1757 if (scale !== 'none' && scale !== '') {1758 const parts = scale.split(' ');1759 transform =1760 (parts.length === 3 ? 'scale3d' : 'scale') +1761 '(' +1762 parts.join(', ') +1763 ') ' +1764 transform;1765 }1766 const rotate: string = computedStyle.rotate;1767 if (rotate !== 'none' && rotate !== '') {1768 const parts = rotate.split(' ');1769 if (parts.length === 1) {1770 transform = 'rotate(' + parts[0] + ') ' + transform;1771 } else if (parts.length === 2) {1772 transform =1773 'rotate' + parts[0].toUpperCase() + '(' + parts[1] + ') ' + transform;1774 } else {1775 transform = 'rotate3d(' + parts.join(', ') + ') ' + transform;1776 }1777 }1778 const translate: string = computedStyle.translate;1779 if (translate !== 'none' && translate !== '') {1780 const parts = translate.split(' ');1781 transform =1782 (parts.length === 3 ? 'translate3d' : 'translate') +1783 '(' +1784 parts.join(', ') +1785 ') ' +1786 transform;1787 }1788 return transform;1789}17901791function moveOutOfViewport(1792 originalStyle: CSSStyleDeclaration,1793 element: HTMLElement,1794): void {1795 // Apply a transform that safely puts the whole element outside the viewport1796 // while still letting it paint its "old" state to a snapshot.1797 const transform = getComputedTransform(originalStyle);1798 // Clear the long form properties.1799 // $FlowFixMe[prop-missing]1800 element.style.translate = 'none';1801 // $FlowFixMe[prop-missing]1802 element.style.scale = 'none';1803 // $FlowFixMe[prop-missing]1804 element.style.rotate = 'none';1805 // Apply a translate to move it way out of the viewport. This is applied first1806 // so that it is in the coordinate space of the parent and not after applying1807 // other transforms. That's why we need to merge the long form properties.1808 // TODO: Ideally we'd adjust for the parent's rotate/scale. Otherwise when1809 // we move back the ::view-transition-group we might overshoot or undershoot.1810 element.style.transform = 'translate(-20000px, -20000px) ' + transform;1811}18121813function moveOldFrameIntoViewport(keyframe: any): void {1814 // In the resulting View Transition Animation, the first frame will be offset.1815 const computedTransform: ?string = keyframe.transform;1816 if (computedTransform != null) {1817 let transform = computedTransform === 'none' ? '' : computedTransform;1818 transform = 'translate(20000px, 20000px) ' + transform;1819 keyframe.transform = transform;1820 }1821}18221823export function cloneRootViewTransitionContainer(1824 rootContainer: Container,1825): Instance {1826 // This implies that we're not going to animate the root document but instead1827 // the clone so we first clear the name of the root container.1828 const documentElement: null | HTMLElement =1829 rootContainer.nodeType === DOCUMENT_NODE1830 ? (rootContainer as any).documentElement1831 : rootContainer.ownerDocument.documentElement;1832 if (1833 documentElement !== null &&1834 // $FlowFixMe[prop-missing]1835 documentElement.style.viewTransitionName === ''1836 ) {1837 // $FlowFixMe[prop-missing]1838 documentElement.style.viewTransitionName = 'none';1839 }18401841 let containerInstance: HTMLElement;1842 if (rootContainer.nodeType === DOCUMENT_NODE) {1843 containerInstance = (rootContainer as any).body;1844 } else if (rootContainer.nodeName === 'HTML') {1845 containerInstance = rootContainer.ownerDocument.body as any;1846 } else if (1847 !disableCommentsAsDOMContainers &&1848 rootContainer.nodeType === COMMENT_NODE1849 ) {1850 throw new Error(1851 'Cannot use a startGestureTransition() with a comment node root.',1852 );1853 } else {1854 // If the container is not the whole document, then we ideally should probably1855 // clone the whole document outside of the React too.1856 containerInstance = rootContainer as any;1857 }18581859 const containerParent = containerInstance.parentNode;1860 if (containerParent === null) {1861 throw new Error(1862 'Cannot use a startGestureTransition() on a detached root.',1863 );1864 }18651866 const clone: HTMLElement = containerInstance.cloneNode(false);18671868 const computedStyle = getComputedStyle(containerInstance);18691870 if (1871 computedStyle.position === 'absolute' ||1872 computedStyle.position === 'fixed'1873 ) {1874 // If the style is already absolute, we don't have to do anything because it'll appear1875 // in the same place.1876 } else {1877 // Otherwise we need to absolutely position the clone in the same location as the original.1878 let positionedAncestor: HTMLElement = containerParent;1879 while (1880 positionedAncestor.parentNode != null &&1881 positionedAncestor.parentNode.nodeType !== DOCUMENT_NODE1882 ) {1883 if (getComputedStyle(positionedAncestor).position !== 'static') {1884 break;1885 }1886 // $FlowFixMe[incompatible-type]: This is refined.1887 positionedAncestor = positionedAncestor.parentNode;1888 }18891890 const positionedAncestorStyle: any = positionedAncestor.style;1891 const containerInstanceStyle: any = containerInstance.style;1892 // Clear the transform while we're measuring since it affects the bounding client rect.1893 const prevAncestorTranslate = positionedAncestorStyle.translate;1894 const prevAncestorScale = positionedAncestorStyle.scale;1895 const prevAncestorRotate = positionedAncestorStyle.rotate;1896 const prevAncestorTransform = positionedAncestorStyle.transform;1897 const prevTranslate = containerInstanceStyle.translate;1898 const prevScale = containerInstanceStyle.scale;1899 const prevRotate = containerInstanceStyle.rotate;1900 const prevTransform = containerInstanceStyle.transform;1901 positionedAncestorStyle.translate = 'none';1902 positionedAncestorStyle.scale = 'none';1903 positionedAncestorStyle.rotate = 'none';1904 positionedAncestorStyle.transform = 'none';1905 containerInstanceStyle.translate = 'none';1906 containerInstanceStyle.scale = 'none';1907 containerInstanceStyle.rotate = 'none';1908 containerInstanceStyle.transform = 'none';19091910 const ancestorRect = positionedAncestor.getBoundingClientRect();1911 const rect = containerInstance.getBoundingClientRect();19121913 const cloneStyle = clone.style;1914 cloneStyle.position = 'absolute';1915 cloneStyle.top = rect.top - ancestorRect.top + 'px';1916 cloneStyle.left = rect.left - ancestorRect.left + 'px';1917 cloneStyle.width = rect.width + 'px';1918 cloneStyle.height = rect.height + 'px';1919 cloneStyle.margin = '0px';1920 cloneStyle.boxSizing = 'border-box';19211922 positionedAncestorStyle.translate = prevAncestorTranslate;1923 positionedAncestorStyle.scale = prevAncestorScale;1924 positionedAncestorStyle.rotate = prevAncestorRotate;1925 positionedAncestorStyle.transform = prevAncestorTransform;1926 containerInstanceStyle.translate = prevTranslate;1927 containerInstanceStyle.scale = prevScale;1928 containerInstanceStyle.rotate = prevRotate;1929 containerInstanceStyle.transform = prevTransform;1930 }19311932 // For this transition the container will act as the root. Nothing outside of it should1933 // be affected anyway. This lets us transition from the cloned container to the original.1934 // $FlowFixMe[prop-missing]1935 clone.style.viewTransitionName = 'root';19361937 // Move out of the viewport so that it's still painted for the snapshot but is not visible1938 // for the frame where the snapshot happens.1939 moveOutOfViewport(computedStyle, clone);19401941 // Insert the clone after the root container as a sibling. This may inject a body1942 // as the next sibling of an existing body. document.body will still point to the1943 // first one and any id selectors will still find the first one. That's why it's1944 // important that it's after the existing node.1945 containerInstance.parentNode.insertBefore(1946 clone,1947 containerInstance.nextSibling,1948 );19491950 return clone;1951}19521953export function removeRootViewTransitionClone(1954 rootContainer: Container,1955 clone: Instance,1956): void {1957 let containerInstance: Instance;1958 if (rootContainer.nodeType === DOCUMENT_NODE) {1959 containerInstance = (rootContainer as any).body;1960 } else if (rootContainer.nodeName === 'HTML') {1961 containerInstance = rootContainer.ownerDocument.body as any;1962 } else {1963 // If the container is not the whole document, then we ideally should probably1964 // clone the whole document outside of the React too.1965 containerInstance = rootContainer as any;1966 }1967 const containerParent = containerInstance.parentNode;1968 if (containerParent === null) {1969 throw new Error(1970 'Cannot use a startGestureTransition() on a detached root.',1971 );1972 }1973 // We assume that the clone is still within the same parent.1974 containerParent.removeChild(clone);19751976 // Now the root is on the containerInstance itself until we call restoreRootViewTransitionName.1977 containerInstance.style.viewTransitionName = 'root';1978}19791980export type InstanceMeasurement = {1981 rect: ClientRect | DOMRect,1982 abs: boolean, // is absolutely positioned1983 clip: boolean, // is a clipping parent1984 view: boolean, // is in viewport bounds1985};19861987function createMeasurement(1988 rect: ClientRect | DOMRect,1989 computedStyle: CSSStyleDeclaration,1990 element: Element,1991): InstanceMeasurement {1992 const ownerWindow = element.ownerDocument.defaultView;1993 return {1994 rect: rect,1995 abs:1996 // Absolutely positioned instances don't contribute their size to the parent.1997 computedStyle.position === 'absolute' ||1998 computedStyle.position === 'fixed',1999 clip:2000 // If a ViewTransition boundary acts as a clipping parent group we should
Findings
✓ No findings reported for this file.