Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions .github/workflows/runtime_build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ jobs:
name: Build DevTools and process artifacts
needs: build_and_lint
runs-on: ubuntu-latest
strategy:
matrix:
browser: [chrome, firefox, edge]
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -525,31 +528,32 @@ jobs:
pattern: _build_*
path: build
merge-multiple: true
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh
- run: ./scripts/ci/pack_and_store_devtools_artifacts.sh ${{ matrix.browser }}
env:
RELEASE_CHANNEL: experimental
- name: Display structure of build
run: ls -R build
- name: Archive devtools build
uses: actions/upload-artifact@v4
with:
name: react-devtools
path: build/devtools.tgz
# Simplifies getting the extension for local testing
- name: Archive chrome extension
- name: Archive ${{ matrix.browser }} extension
uses: actions/upload-artifact@v4
with:
name: react-devtools-chrome-extension
path: build/devtools/chrome-extension.zip
- name: Archive firefox extension
uses: actions/upload-artifact@v4
name: react-devtools-${{ matrix.browser }}-extension
path: build/devtools/${{ matrix.browser }}-extension.zip

merge_devtools_artifacts:
name: Merge DevTools artifacts
needs: build_devtools_and_process_artifacts
runs-on: ubuntu-latest
steps:
- name: Merge artifacts
uses: actions/upload-artifact/merge@v4
with:
name: react-devtools-firefox-extension
path: build/devtools/firefox-extension.zip
name: react-devtools
pattern: react-devtools-*-extension

run_devtools_e2e_tests:
name: Run DevTools e2e tests
needs: build_devtools_and_process_artifacts
needs: build_and_lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 5 additions & 7 deletions packages/react-reconciler/src/ReactFiberBeginWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import {
Passive,
DidDefer,
ViewTransitionNamedStatic,
ViewTransitionNamedMount,
LayoutStatic,
} from './ReactFiberFlags';
import {
Expand Down Expand Up @@ -266,7 +267,6 @@ import {
markSkippedUpdateLanes,
getWorkInProgressRoot,
peekDeferredLane,
trackAppearingViewTransition,
} from './ReactFiberWorkLoop';
import {enqueueConcurrentRenderForLane} from './ReactFiberConcurrentUpdates';
import {pushCacheProvider, CacheContext} from './ReactFiberCacheComponent';
Expand Down Expand Up @@ -3243,12 +3243,10 @@ function updateViewTransition(
if (pendingProps.name != null && pendingProps.name !== 'auto') {
// Explicitly named boundary. We track it so that we can pair it up with another explicit
// boundary if we get deleted.
workInProgress.flags |= ViewTransitionNamedStatic;
if (current === null) {
// This is a new mount. We track it in case we end up having a deletion with the same name.
// TODO: A problem with this strategy is that this subtree might not actually end up mounted.
trackAppearingViewTransition(instance, pendingProps.name);
}
workInProgress.flags |=
current === null
? ViewTransitionNamedMount | ViewTransitionNamedStatic
: ViewTransitionNamedStatic;
} else {
// Assign an auto generated name using the useId algorthim if an explicit one is not provided.
// We don't need the name yet but we do it here to allow hydration state to be used.
Expand Down
83 changes: 53 additions & 30 deletions packages/react-reconciler/src/ReactFiberCommitWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,10 @@ export let shouldFireAfterActiveInstanceBlur: boolean = false;

export let shouldStartViewTransition: boolean = false;

// This tracks named ViewTransition components found in the accumulateSuspenseyCommit
// phase that might need to find deleted pairs in the beforeMutation phase.
let appearingViewTransitions: Map<string, ViewTransitionState> | null = null;

// Used during the commit phase to track whether a parent ViewTransition component
// might have been affected by any mutations / relayouts below.
let viewTransitionContextChanged: boolean = false;
Expand All @@ -288,7 +292,6 @@ export function commitBeforeMutationEffects(
root: FiberRoot,
firstChild: Fiber,
committedLanes: Lanes,
appearingViewTransitions: Map<string, ViewTransitionState> | null,
): void {
focusedInstanceHandle = prepareForCommit(root.containerInfo);
shouldFireAfterActiveInstanceBlur = false;
Expand All @@ -299,19 +302,15 @@ export function commitBeforeMutationEffects(
includesOnlyViewTransitionEligibleLanes(committedLanes);

nextEffect = firstChild;
commitBeforeMutationEffects_begin(
isViewTransitionEligible,
appearingViewTransitions,
);
commitBeforeMutationEffects_begin(isViewTransitionEligible);

// We no longer need to track the active instance fiber
focusedInstanceHandle = null;
// We've found any matched pairs and can now reset.
appearingViewTransitions = null;
}

function commitBeforeMutationEffects_begin(
isViewTransitionEligible: boolean,
appearingViewTransitions: Map<string, ViewTransitionState> | null,
) {
function commitBeforeMutationEffects_begin(isViewTransitionEligible: boolean) {
// If this commit is eligible for a View Transition we look into all mutated subtrees.
// TODO: We could optimize this by marking these with the Snapshot subtree flag in the render phase.
const subtreeMask = isViewTransitionEligible
Expand All @@ -331,7 +330,6 @@ function commitBeforeMutationEffects_begin(
commitBeforeMutationEffectsDeletion(
deletion,
isViewTransitionEligible,
appearingViewTransitions,
);
}
}
Expand Down Expand Up @@ -364,7 +362,7 @@ function commitBeforeMutationEffects_begin(
isViewTransitionEligible
) {
// Was previously mounted as visible but is now hidden.
commitExitViewTransitions(current, appearingViewTransitions);
commitExitViewTransitions(current);
}
// Skip before mutation effects of the children because they're hidden.
commitBeforeMutationEffects_complete(isViewTransitionEligible);
Expand Down Expand Up @@ -528,7 +526,6 @@ function commitBeforeMutationEffectsOnFiber(
function commitBeforeMutationEffectsDeletion(
deletion: Fiber,
isViewTransitionEligible: boolean,
appearingViewTransitions: Map<string, ViewTransitionState> | null,
) {
if (enableCreateEventHandleAPI) {
// TODO (effects) It would be nice to avoid calling doesFiberContain()
Expand All @@ -541,7 +538,7 @@ function commitBeforeMutationEffectsDeletion(
}
}
if (isViewTransitionEligible) {
commitExitViewTransitions(deletion, appearingViewTransitions);
commitExitViewTransitions(deletion);
}
}

Expand Down Expand Up @@ -745,14 +742,15 @@ function commitEnterViewTransitions(placement: Fiber): void {
}
}

function commitDeletedPairViewTransitions(
deletion: Fiber,
appearingViewTransitions: Map<string, ViewTransitionState>,
): void {
if (appearingViewTransitions.size === 0) {
function commitDeletedPairViewTransitions(deletion: Fiber): void {
if (
appearingViewTransitions === null ||
appearingViewTransitions.size === 0
) {
// We've found all.
return;
}
const pairs = appearingViewTransitions;
if ((deletion.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
// This has no named view transitions in its subtree.
return;
Expand All @@ -769,7 +767,7 @@ function commitDeletedPairViewTransitions(
const props: ViewTransitionProps = child.memoizedProps;
const name = props.name;
if (name != null && name !== 'auto') {
const pair = appearingViewTransitions.get(name);
const pair = pairs.get(name);
if (pair !== undefined) {
const className: ?string = getViewTransitionClassName(
props.className,
Expand Down Expand Up @@ -802,23 +800,20 @@ function commitDeletedPairViewTransitions(
}
// Delete the entry so that we know when we've found all of them
// and can stop searching (size reaches zero).
appearingViewTransitions.delete(name);
if (appearingViewTransitions.size === 0) {
pairs.delete(name);
if (pairs.size === 0) {
break;
}
}
}
}
commitDeletedPairViewTransitions(child, appearingViewTransitions);
commitDeletedPairViewTransitions(child);
}
child = child.sibling;
}
}

function commitExitViewTransitions(
deletion: Fiber,
appearingViewTransitions: Map<string, ViewTransitionState> | null,
): void {
function commitExitViewTransitions(deletion: Fiber): void {
if (deletion.tag === ViewTransitionComponent) {
const props: ViewTransitionProps = deletion.memoizedProps;
const name = getViewTransitionName(props, deletion.stateNode);
Expand Down Expand Up @@ -863,17 +858,17 @@ function commitExitViewTransitions(
}
if (appearingViewTransitions !== null) {
// Look for more pairs deeper in the tree.
commitDeletedPairViewTransitions(deletion, appearingViewTransitions);
commitDeletedPairViewTransitions(deletion);
}
} else if ((deletion.subtreeFlags & ViewTransitionStatic) !== NoFlags) {
let child = deletion.child;
while (child !== null) {
commitExitViewTransitions(child, appearingViewTransitions);
commitExitViewTransitions(child);
child = child.sibling;
}
} else {
if (appearingViewTransitions !== null) {
commitDeletedPairViewTransitions(deletion, appearingViewTransitions);
commitDeletedPairViewTransitions(deletion);
}
}
}
Expand Down Expand Up @@ -1212,7 +1207,7 @@ function measureUpdateViewTransition(
);
const layoutClassName: ?string = getViewTransitionClassName(
props.className,
props.update,
props.layout,
);
let className: ?string;
if (updateClassName === 'none') {
Expand Down Expand Up @@ -4813,8 +4808,13 @@ export function commitPassiveUnmountEffects(finishedWork: Fiber): void {
// already in the "current" tree. Because their visibility has changed, the
// browser may not have prerendered them yet. So we check the MaySuspendCommit
// flag instead.
//
// Note that MaySuspendCommit and ShouldSuspendCommit also includes named
// ViewTransitions so that we know to also visit those to collect appearing
// pairs.
let suspenseyCommitFlag = ShouldSuspendCommit;
export function accumulateSuspenseyCommit(finishedWork: Fiber): void {
appearingViewTransitions = null;
accumulateSuspenseyCommitOnFiber(finishedWork);
}

Expand Down Expand Up @@ -4893,6 +4893,29 @@ function accumulateSuspenseyCommitOnFiber(fiber: Fiber) {
}
break;
}
case ViewTransitionComponent: {
if (enableViewTransition) {
if ((fiber.flags & suspenseyCommitFlag) !== NoFlags) {
const props: ViewTransitionProps = fiber.memoizedProps;
const name: ?string | 'auto' = props.name;
if (name != null && name !== 'auto') {
// This is a named ViewTransition being mounted or reappearing. Let's add it to
// the map so we can match it with deletions later.
if (appearingViewTransitions === null) {
appearingViewTransitions = new Map();
}
// Reset the pair in case we didn't end up restoring the instance in previous commits.
// This shouldn't really happen anymore but just in case. We could maybe add an invariant.
const instance: ViewTransitionState = fiber.stateNode;
instance.paired = null;
appearingViewTransitions.set(name, instance);
}
}
recursivelyAccumulateSuspenseyCommit(fiber);
break;
}
// Fallthrough
}
default: {
recursivelyAccumulateSuspenseyCommit(fiber);
}
Expand Down
42 changes: 0 additions & 42 deletions packages/react-reconciler/src/ReactFiberCompleteWork.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,6 @@ import type {
OffscreenState,
OffscreenQueue,
} from './ReactFiberActivityComponent';
import type {
ViewTransitionProps,
ViewTransitionState,
} from './ReactFiberViewTransitionComponent';
import {isOffscreenManual} from './ReactFiberActivityComponent';
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent';
import type {Cache} from './ReactFiberCacheComponent';
Expand Down Expand Up @@ -99,7 +95,6 @@ import {
ShouldSuspendCommit,
Cloned,
ViewTransitionStatic,
ViewTransitionNamedStatic,
} from './ReactFiberFlags';

import {
Expand Down Expand Up @@ -164,7 +159,6 @@ import {
getWorkInProgressTransitions,
shouldRemainOnPreviousScreen,
markSpawnedRetryLane,
trackAppearingViewTransition,
} from './ReactFiberWorkLoop';
import {
OffscreenLane,
Expand Down Expand Up @@ -947,34 +941,6 @@ function completeDehydratedSuspenseBoundary(
}
}

function trackReappearingViewTransitions(workInProgress: Fiber): void {
if ((workInProgress.subtreeFlags & ViewTransitionNamedStatic) === NoFlags) {
// This has no named view transitions in its subtree.
return;
}
// This needs to search for any explicitly named reappearing View Transitions,
// whether they were updated in this transition or unchanged from before.
let child = workInProgress.child;
while (child !== null) {
if (child.tag === OffscreenComponent && child.memoizedState === null) {
// This tree is currently hidden so we skip it.
} else {
if (
child.tag === ViewTransitionComponent &&
(child.flags & ViewTransitionNamedStatic) !== NoFlags
) {
const props: ViewTransitionProps = child.memoizedProps;
if (props.name != null && props.name !== 'auto') {
const instance: ViewTransitionState = child.stateNode;
trackAppearingViewTransition(instance, props.name);
}
}
trackReappearingViewTransitions(child);
}
child = child.sibling;
}
}

function completeWork(
current: Fiber | null,
workInProgress: Fiber,
Expand Down Expand Up @@ -1796,14 +1762,6 @@ function completeWork(
const prevIsHidden = prevState !== null;
if (prevIsHidden !== nextIsHidden) {
workInProgress.flags |= Visibility;
if (enableViewTransition && !nextIsHidden) {
// If we're revealing a new tree, we need to find any named
// ViewTransitions inside it that might have a deleted pair.
// We do this in the complete phase in case the tree has
// changed during the reveal but we have to do it before we
// find the first deleted pair in the before mutation phase.
trackReappearingViewTransitions(workInProgress);
}
}
} else {
// On initial mount, we only need a Visibility effect if the tree
Expand Down
7 changes: 5 additions & 2 deletions packages/react-reconciler/src/ReactFiberFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const StoreConsistency = /* */ 0b0000000000000000100000000000
// possible, because we're about to run out of bits.
export const ScheduleRetry = StoreConsistency;
export const ShouldSuspendCommit = Visibility;
export const ViewTransitionNamedMount = ShouldSuspendCommit;
export const DidDefer = ContentReset;
export const FormReset = Snapshot;
export const AffectedParentLayout = ContentReset;
Expand Down Expand Up @@ -74,8 +75,10 @@ export const PassiveStatic = /* */ 0b0000000100000000000000000000
export const MaySuspendCommit = /* */ 0b0000001000000000000000000000000;
// ViewTransitionNamedStatic tracks explicitly name ViewTransition components deeply
// that might need to be visited during clean up. This is similar to SnapshotStatic
// if there was any other use for it.
export const ViewTransitionNamedStatic = /* */ SnapshotStatic;
// if there was any other use for it. It also needs to run in the same phase as
// MaySuspendCommit tracking.
export const ViewTransitionNamedStatic =
/* */ SnapshotStatic | MaySuspendCommit;
// ViewTransitionStatic tracks whether there are an ViewTransition components from
// the nearest HostComponent down. It resets at every HostComponent level.
export const ViewTransitionStatic = /* */ 0b0000010000000000000000000000000;
Expand Down
Loading
Loading