@@ -366,6 +366,7 @@ export function getInternalReactConstants(version: string): {
366366 ReactPriorityLevels : ReactPriorityLevelsType ,
367367 ReactTypeOfWork : WorkTagMap ,
368368 StrictModeBits : number ,
369+ SuspenseyImagesMode : number ,
369370} {
370371 // **********************************************************
371372 // The section below is copied from files in React repo.
@@ -406,6 +407,8 @@ export function getInternalReactConstants(version: string): {
406407 StrictModeBits = 0b10 ;
407408 }
408409
410+ const SuspenseyImagesMode = 0b0100000 ;
411+
409412 let ReactTypeOfWork : WorkTagMap = ( ( null : any ) : WorkTagMap ) ;
410413
411414 // **********************************************************
@@ -819,6 +822,7 @@ export function getInternalReactConstants(version: string): {
819822 ReactPriorityLevels,
820823 ReactTypeOfWork,
821824 StrictModeBits,
825+ SuspenseyImagesMode,
822826 };
823827}
824828
@@ -987,6 +991,7 @@ export function attach(
987991 ReactPriorityLevels,
988992 ReactTypeOfWork,
989993 StrictModeBits,
994+ SuspenseyImagesMode,
990995 } = getInternalReactConstants(version);
991996 const {
992997 ActivityComponent,
@@ -3307,6 +3312,114 @@ export function attach(
33073312 insertSuspendedBy(asyncInfo);
33083313 }
33093314
3315+ function trackDebugInfoFromHostComponent(
3316+ devtoolsInstance: DevToolsInstance,
3317+ fiber: Fiber,
3318+ ): void {
3319+ if (fiber.tag !== HostComponent) {
3320+ return;
3321+ }
3322+ if ((fiber.mode & SuspenseyImagesMode) === 0) {
3323+ // In any released version, Suspensey Images are only enabled inside a ViewTransition
3324+ // subtree, which is enabled by the SuspenseyImagesMode.
3325+ // TODO: If we ever enable the enableSuspenseyImages flag then it would be enabled for
3326+ // all images and we'd need some other check for if the version of React has that enabled.
3327+ return;
3328+ }
3329+
3330+ const type = fiber.type;
3331+ const props: {
3332+ src?: string,
3333+ onLoad?: (event: any) => void,
3334+ loading?: 'eager' | 'lazy',
3335+ ...
3336+ } = fiber.memoizedProps;
3337+
3338+ const maySuspendCommit =
3339+ type === 'img' &&
3340+ props.src != null &&
3341+ props.src !== '' &&
3342+ props.onLoad == null &&
3343+ props.loading !== 'lazy';
3344+
3345+ // Note: We don't track "maySuspendCommitOnUpdate" separately because it doesn't matter if
3346+ // it didn't suspend this particular update if it would've suspended if it mounted in this
3347+ // state, since we're tracking the dependencies inside the current state.
3348+
3349+ if (!maySuspendCommit) {
3350+ return;
3351+ }
3352+
3353+ const instance = fiber.stateNode;
3354+ if (instance == null) {
3355+ // Should never happen.
3356+ return;
3357+ }
3358+
3359+ // Unlike props.src, currentSrc will be fully qualified which we need for comparison below.
3360+ // Unlike instance.src it will be resolved into the media queries currently matching which is
3361+ // the state we're inspecting.
3362+ const src = instance.currentSrc;
3363+ if (typeof src !== 'string' || src === '') {
3364+ return;
3365+ }
3366+ let start = -1;
3367+ let end = -1;
3368+ let fileSize = 0;
3369+ // $FlowFixMe[method-unbinding]
3370+ if (typeof performance.getEntriesByType === 'function') {
3371+ // We may be able to collect the start and end time of this resource from Performance Observer.
3372+ const resourceEntries = performance.getEntriesByType('resource');
3373+ for (let i = 0; i < resourceEntries.length; i++) {
3374+ const resourceEntry = resourceEntries[i];
3375+ if (resourceEntry.name === src) {
3376+ start = resourceEntry.startTime;
3377+ end = start + resourceEntry.duration;
3378+ // $FlowFixMe[prop-missing]
3379+ fileSize = (resourceEntry.encodedBodySize: any) || 0;
3380+ }
3381+ }
3382+ }
3383+ // A representation of the image data itself.
3384+ // TODO: We could render a little preview in the front end from the resource API.
3385+ const value: {
3386+ currentSrc: string,
3387+ naturalWidth?: number,
3388+ naturalHeight?: number,
3389+ fileSize?: number,
3390+ } = {
3391+ currentSrc: src,
3392+ };
3393+ if (instance.naturalWidth > 0 && instance.naturalHeight > 0) {
3394+ // The intrinsic size of the file value itself, if it's loaded
3395+ value.naturalWidth = instance.naturalWidth;
3396+ value.naturalHeight = instance.naturalHeight;
3397+ }
3398+ if (fileSize > 0) {
3399+ // Cross-origin images won't have a file size that we can access.
3400+ value.fileSize = fileSize;
3401+ }
3402+ const promise = Promise.resolve(value);
3403+ (promise: any).status = 'fulfilled';
3404+ (promise: any).value = value;
3405+ const ioInfo: ReactIOInfo = {
3406+ name: 'img',
3407+ start,
3408+ end,
3409+ value: promise,
3410+ // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file.
3411+ owner: fiber, // Allow linking to the <link> if it's not filtered.
3412+ };
3413+ const asyncInfo: ReactAsyncInfo = {
3414+ awaited: ioInfo,
3415+ // $FlowFixMe: This field doesn't usually take a Fiber but we're only using inside this file.
3416+ owner: fiber._debugOwner == null ? null : fiber._debugOwner,
3417+ debugStack: fiber._debugStack == null ? null : fiber._debugStack,
3418+ debugTask: fiber._debugTask == null ? null : fiber._debugTask,
3419+ };
3420+ insertSuspendedBy(asyncInfo);
3421+ }
3422+
33103423 function mountVirtualChildrenRecursively(
33113424 firstChild: Fiber,
33123425 lastChild: null | Fiber, // non-inclusive
@@ -3545,6 +3658,7 @@ export function attach(
35453658 throw new Error('Did not expect a host hoistable to be the root');
35463659 }
35473660 aquireHostInstance(nearestInstance, fiber.stateNode);
3661+ trackDebugInfoFromHostComponent(nearestInstance, fiber);
35483662 }
35493663
35503664 if (fiber.tag === OffscreenComponent && fiber.memoizedState !== null) {
@@ -4373,20 +4487,22 @@ export function attach(
43734487 aquireHostResource(nearestInstance, nextFiber.memoizedState);
43744488 trackDebugInfoFromHostResource(nearestInstance, nextFiber);
43754489 } else if (
4376- (nextFiber.tag === HostComponent ||
4377- nextFiber.tag === HostText ||
4378- nextFiber.tag === HostSingleton) &&
4379- prevFiber.stateNode !== nextFiber.stateNode
4490+ nextFiber.tag === HostComponent ||
4491+ nextFiber.tag === HostText ||
4492+ nextFiber.tag === HostSingleton
43804493 ) {
4381- // In persistent mode, it's possible for the stateNode to update with
4382- // a new clone. In that case we need to release the old one and aquire
4383- // new one instead.
43844494 const nearestInstance = reconcilingParent;
43854495 if (nearestInstance === null) {
43864496 throw new Error('Did not expect a host hoistable to be the root');
43874497 }
4388- releaseHostInstance(nearestInstance, prevFiber.stateNode);
4389- aquireHostInstance(nearestInstance, nextFiber.stateNode);
4498+ if (prevFiber.stateNode !== nextFiber.stateNode) {
4499+ // In persistent mode, it's possible for the stateNode to update with
4500+ // a new clone. In that case we need to release the old one and aquire
4501+ // new one instead.
4502+ releaseHostInstance(nearestInstance, prevFiber.stateNode);
4503+ aquireHostInstance(nearestInstance, nextFiber.stateNode);
4504+ }
4505+ trackDebugInfoFromHostComponent(nearestInstance, nextFiber);
43904506 }
43914507
43924508 let updateFlags = NoUpdate;
0 commit comments