mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Replay capture phase for continuous events (#22856)
Co-authored-by: Dan Abramov <dan.abramov@me.com> Co-authored-by: Marco Salazar <salazarm@fb.com>
This commit is contained in:
co-authored by
Dan Abramov
Marco Salazar
parent
44f99d75fa
commit
71d16750c5
+521
-8
@@ -684,7 +684,6 @@ describe('ReactDOMServerSelectiveHydration', () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
|
||||
function Child({text}) {
|
||||
if ((text === 'A' || text === 'D') && suspend) {
|
||||
throw promise;
|
||||
@@ -724,11 +723,8 @@ describe('ReactDOMServerSelectiveHydration', () => {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
|
||||
|
||||
const container = document.createElement('div');
|
||||
// We need this to be in the document since we'll dispatch events on it.
|
||||
document.body.appendChild(container);
|
||||
@@ -748,22 +744,18 @@ describe('ReactDOMServerSelectiveHydration', () => {
|
||||
|
||||
// Nothing has been hydrated so far.
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
// Click D
|
||||
dispatchMouseHoverEvent(spanD, null);
|
||||
dispatchClickEvent(spanD);
|
||||
// Hover over B and then C.
|
||||
dispatchMouseHoverEvent(spanB, spanD);
|
||||
dispatchMouseHoverEvent(spanC, spanB);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App']);
|
||||
|
||||
await act(async () => {
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
});
|
||||
|
||||
if (
|
||||
gate(
|
||||
flags =>
|
||||
@@ -799,6 +791,527 @@ describe('ReactDOMServerSelectiveHydration', () => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('replays capture phase for continuous events and respects stopPropagation', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
const promise = new Promise(resolvePromise => (resolve = resolvePromise));
|
||||
|
||||
function Child({text}) {
|
||||
if ((text === 'A' || text === 'D') && suspend) {
|
||||
throw promise;
|
||||
}
|
||||
Scheduler.unstable_yieldValue(text);
|
||||
return (
|
||||
<span
|
||||
id={text}
|
||||
onClickCapture={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Capture Clicked ' + text);
|
||||
}}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Clicked ' + text);
|
||||
}}
|
||||
onMouseEnter={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Mouse Enter ' + text);
|
||||
}}
|
||||
onMouseOut={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Mouse Out ' + text);
|
||||
}}
|
||||
onMouseOutCapture={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Scheduler.unstable_yieldValue('Mouse Out Capture ' + text);
|
||||
}}
|
||||
onMouseOverCapture={e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
Scheduler.unstable_yieldValue('Mouse Over Capture ' + text);
|
||||
}}
|
||||
onMouseOver={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Mouse Over ' + text);
|
||||
}}>
|
||||
<div
|
||||
onMouseOverCapture={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Mouse Over Capture Inner ' + text);
|
||||
}}>
|
||||
{text}
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
Scheduler.unstable_yieldValue('App');
|
||||
return (
|
||||
<div
|
||||
onClickCapture={e => {
|
||||
e.preventDefault();
|
||||
Scheduler.unstable_yieldValue('Capture Clicked Parent');
|
||||
}}
|
||||
onMouseOverCapture={e => {
|
||||
Scheduler.unstable_yieldValue('Mouse Over Capture Parent');
|
||||
}}>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child text="A" />
|
||||
</Suspense>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child text="B" />
|
||||
</Suspense>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child text="C" />
|
||||
</Suspense>
|
||||
<Suspense fallback="Loading...">
|
||||
<Child text="D" />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App', 'A', 'B', 'C', 'D']);
|
||||
|
||||
const container = document.createElement('div');
|
||||
// We need this to be in the document since we'll dispatch events on it.
|
||||
document.body.appendChild(container);
|
||||
|
||||
container.innerHTML = finalHTML;
|
||||
|
||||
const spanB = document.getElementById('B').firstChild;
|
||||
const spanC = document.getElementById('C').firstChild;
|
||||
const spanD = document.getElementById('D').firstChild;
|
||||
|
||||
suspend = true;
|
||||
|
||||
// A and D will be suspended. We'll click on D which should take
|
||||
// priority, after we unsuspend.
|
||||
ReactDOM.hydrateRoot(container, <App />);
|
||||
|
||||
// Nothing has been hydrated so far.
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
// Click D
|
||||
dispatchMouseHoverEvent(spanD, null);
|
||||
dispatchClickEvent(spanD);
|
||||
// Hover over B and then C.
|
||||
dispatchMouseHoverEvent(spanB, spanD);
|
||||
dispatchMouseHoverEvent(spanC, spanB);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App']);
|
||||
|
||||
await act(async () => {
|
||||
suspend = false;
|
||||
resolve();
|
||||
await promise;
|
||||
});
|
||||
|
||||
if (
|
||||
gate(
|
||||
flags =>
|
||||
flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
|
||||
)
|
||||
) {
|
||||
// We should prioritize hydrating D first because we clicked it.
|
||||
// but event isnt replayed
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'D',
|
||||
'B', // Ideally this should be later.
|
||||
'C',
|
||||
// Mouse out events aren't replayed
|
||||
// 'Mouse Out Capture B',
|
||||
// 'Mouse Out B',
|
||||
'Mouse Over Capture Parent',
|
||||
'Mouse Over Capture C',
|
||||
// Stop propagation stops these
|
||||
// 'Mouse Over Capture Inner C',
|
||||
// 'Mouse Over C',
|
||||
'A',
|
||||
]);
|
||||
} else {
|
||||
// We should prioritize hydrating D first because we clicked it.
|
||||
// Next we should hydrate C since that's the current hover target.
|
||||
// To simplify implementation details we hydrate both B and C at
|
||||
// the same time since B was already scheduled.
|
||||
// This is ok because it will at least not continue for nested
|
||||
// boundary. See the next test below.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'D',
|
||||
'Clicked D',
|
||||
'B', // Ideally this should be later.
|
||||
'C',
|
||||
// Capture phase isn't replayed
|
||||
// Mouseout isn't replayed
|
||||
'Mouse Over C',
|
||||
'Mouse Enter C',
|
||||
'A',
|
||||
]);
|
||||
}
|
||||
|
||||
// This test shows existing quirk where stopPropagation on mouseout
|
||||
// prevents mouseEnter from firing
|
||||
dispatchMouseHoverEvent(spanC, spanB);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Mouse Out Capture B',
|
||||
// stopPropagation stops these
|
||||
// 'Mouse Out B',
|
||||
// 'Mouse Enter C',
|
||||
'Mouse Over Capture Parent',
|
||||
'Mouse Over Capture C',
|
||||
// Stop propagation stops these
|
||||
// 'Mouse Over Capture Inner C',
|
||||
// 'Mouse Over C',
|
||||
]);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
describe('can handle replaying events as part of multiple instances of React', () => {
|
||||
let resolveInner;
|
||||
let resolveOuter;
|
||||
let innerPromise;
|
||||
let outerPromise;
|
||||
let OuterScheduler;
|
||||
let InnerScheduler;
|
||||
let innerDiv;
|
||||
|
||||
beforeEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
jest.resetModuleRegistry();
|
||||
let OuterReactDOM;
|
||||
let InnerReactDOM;
|
||||
jest.isolateModules(() => {
|
||||
OuterReactDOM = require('react-dom');
|
||||
OuterScheduler = require('scheduler');
|
||||
});
|
||||
jest.isolateModules(() => {
|
||||
InnerReactDOM = require('react-dom');
|
||||
InnerScheduler = require('scheduler');
|
||||
});
|
||||
|
||||
expect(OuterReactDOM).not.toBe(InnerReactDOM);
|
||||
expect(OuterScheduler).not.toBe(InnerScheduler);
|
||||
|
||||
const outerContainer = document.createElement('div');
|
||||
const innerContainer = document.createElement('div');
|
||||
|
||||
let suspendOuter = false;
|
||||
outerPromise = new Promise(res => {
|
||||
resolveOuter = () => {
|
||||
suspendOuter = false;
|
||||
res();
|
||||
};
|
||||
});
|
||||
|
||||
function Outer() {
|
||||
if (suspendOuter) {
|
||||
OuterScheduler.unstable_yieldValue('Suspend Outer');
|
||||
throw outerPromise;
|
||||
}
|
||||
OuterScheduler.unstable_yieldValue('Outer');
|
||||
const innerRoot = outerContainer.querySelector('#inner-root');
|
||||
return (
|
||||
<div
|
||||
id="inner-root"
|
||||
onMouseEnter={() => {
|
||||
Scheduler.unstable_yieldValue('Outer Mouse Enter');
|
||||
}}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: innerRoot ? innerRoot.innerHTML : '',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const OuterApp = () => {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Outer />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
let suspendInner = false;
|
||||
innerPromise = new Promise(res => {
|
||||
resolveInner = () => {
|
||||
suspendInner = false;
|
||||
res();
|
||||
};
|
||||
});
|
||||
function Inner() {
|
||||
if (suspendInner) {
|
||||
InnerScheduler.unstable_yieldValue('Suspend Inner');
|
||||
throw innerPromise;
|
||||
}
|
||||
InnerScheduler.unstable_yieldValue('Inner');
|
||||
return (
|
||||
<div
|
||||
id="inner"
|
||||
onMouseEnter={() => {
|
||||
Scheduler.unstable_yieldValue('Inner Mouse Enter');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
const InnerApp = () => {
|
||||
return (
|
||||
<Suspense fallback={<div>Loading</div>}>
|
||||
<Inner />
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
||||
document.body.appendChild(outerContainer);
|
||||
const outerHTML = ReactDOMServer.renderToString(<OuterApp />);
|
||||
outerContainer.innerHTML = outerHTML;
|
||||
|
||||
const innerWrapper = document.querySelector('#inner-root');
|
||||
innerWrapper.appendChild(innerContainer);
|
||||
const innerHTML = ReactDOMServer.renderToString(<InnerApp />);
|
||||
innerContainer.innerHTML = innerHTML;
|
||||
|
||||
expect(OuterScheduler).toHaveYielded(['Outer']);
|
||||
expect(InnerScheduler).toHaveYielded(['Inner']);
|
||||
|
||||
suspendOuter = true;
|
||||
suspendInner = true;
|
||||
|
||||
OuterReactDOM.hydrateRoot(outerContainer, <OuterApp />);
|
||||
InnerReactDOM.hydrateRoot(innerContainer, <InnerApp />);
|
||||
|
||||
expect(OuterScheduler).toFlushAndYield(['Suspend Outer']);
|
||||
expect(InnerScheduler).toFlushAndYield(['Suspend Inner']);
|
||||
|
||||
innerDiv = document.querySelector('#inner');
|
||||
|
||||
dispatchClickEvent(innerDiv);
|
||||
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
expect(OuterScheduler).toHaveYielded(['Suspend Outer']);
|
||||
if (
|
||||
gate(
|
||||
flags =>
|
||||
flags.enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
|
||||
)
|
||||
) {
|
||||
// InnerApp doesn't see the event because OuterApp calls stopPropagation in
|
||||
// capture phase since the event is blocked on suspended component
|
||||
expect(InnerScheduler).toHaveYielded([]);
|
||||
} else {
|
||||
// no stopPropagation
|
||||
expect(InnerScheduler).toHaveYielded(['Suspend Inner']);
|
||||
}
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
});
|
||||
afterEach(async () => {
|
||||
document.body.innerHTML = '';
|
||||
});
|
||||
|
||||
// @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
|
||||
it('Inner hydrates first then Outer', async () => {
|
||||
dispatchMouseHoverEvent(innerDiv);
|
||||
|
||||
await act(async () => {
|
||||
resolveInner();
|
||||
await innerPromise;
|
||||
jest.runAllTimers();
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
expect(OuterScheduler).toHaveYielded(['Suspend Outer']);
|
||||
// Inner App renders because it is unblocked
|
||||
expect(InnerScheduler).toHaveYielded(['Inner']);
|
||||
// No event is replayed yet
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
dispatchMouseHoverEvent(innerDiv);
|
||||
expect(OuterScheduler).toHaveYielded([]);
|
||||
expect(InnerScheduler).toHaveYielded([]);
|
||||
// No event is replayed yet
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
await act(async () => {
|
||||
resolveOuter();
|
||||
await outerPromise;
|
||||
jest.runAllTimers();
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// Nothing happens to inner app yet.
|
||||
// Its blocked on the outer app replaying the event
|
||||
expect(InnerScheduler).toHaveYielded([]);
|
||||
// Outer hydrates and schedules Replay
|
||||
expect(OuterScheduler).toHaveYielded(['Outer']);
|
||||
// No event is replayed yet
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
// fire scheduled Replay
|
||||
await act(async () => {
|
||||
jest.runAllTimers();
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// First Inner Mouse Enter fires then Outer Mouse Enter
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Inner Mouse Enter',
|
||||
'Outer Mouse Enter',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
|
||||
it('Outer hydrates first then Inner', async () => {
|
||||
dispatchMouseHoverEvent(innerDiv);
|
||||
|
||||
await act(async () => {
|
||||
resolveOuter();
|
||||
await outerPromise;
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// Outer resolves and scheduled replay
|
||||
expect(OuterScheduler).toHaveYielded(['Outer']);
|
||||
// Inner App is still blocked
|
||||
expect(InnerScheduler).toHaveYielded([]);
|
||||
|
||||
// Replay outer event
|
||||
await act(async () => {
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// Inner is still blocked so when Outer replays the event in capture phase
|
||||
// inner ends up caling stopPropagation
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
expect(OuterScheduler).toHaveYielded([]);
|
||||
expect(InnerScheduler).toHaveYielded(['Suspend Inner']);
|
||||
|
||||
dispatchMouseHoverEvent(innerDiv);
|
||||
expect(OuterScheduler).toHaveYielded([]);
|
||||
expect(InnerScheduler).toHaveYielded([]);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
await act(async () => {
|
||||
resolveInner();
|
||||
await innerPromise;
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// Inner hydrates
|
||||
expect(InnerScheduler).toHaveYielded(['Inner']);
|
||||
// Outer was hydrated earlier
|
||||
expect(OuterScheduler).toHaveYielded([]);
|
||||
|
||||
await act(async () => {
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
OuterScheduler.unstable_flushAllWithoutAsserting();
|
||||
InnerScheduler.unstable_flushAllWithoutAsserting();
|
||||
});
|
||||
|
||||
// First Inner Mouse Enter fires then Outer Mouse Enter
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Inner Mouse Enter',
|
||||
'Outer Mouse Enter',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay
|
||||
it('replays event with null target when tree is dismounted', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
const promise = new Promise(resolvePromise => {
|
||||
resolve = () => {
|
||||
suspend = false;
|
||||
resolvePromise();
|
||||
};
|
||||
});
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
}
|
||||
Scheduler.unstable_yieldValue('Child');
|
||||
return (
|
||||
<div
|
||||
onMouseOver={() => {
|
||||
Scheduler.unstable_yieldValue('on mouse over');
|
||||
}}>
|
||||
Child
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Suspense>
|
||||
<Child />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
const finalHTML = ReactDOMServer.renderToString(<App />);
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.innerHTML = finalHTML;
|
||||
suspend = true;
|
||||
|
||||
ReactDOM.hydrateRoot(container, <App />);
|
||||
|
||||
const childDiv = container.firstElementChild;
|
||||
dispatchMouseHoverEvent(childDiv);
|
||||
|
||||
// Not hydrated so event is saved for replay and stopPropagation is called
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
|
||||
resolve();
|
||||
Scheduler.unstable_flushNumberOfYields(1);
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
|
||||
Scheduler.unstable_scheduleCallback(
|
||||
Scheduler.unstable_ImmediatePriority,
|
||||
() => {
|
||||
container.removeChild(childDiv);
|
||||
|
||||
const container2 = document.createElement('div');
|
||||
container2.addEventListener('mouseover', () => {
|
||||
Scheduler.unstable_yieldValue('container2 mouse over');
|
||||
});
|
||||
container2.appendChild(childDiv);
|
||||
},
|
||||
);
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
|
||||
// Even though the tree is remove the event is still dispatched with native event handler
|
||||
// on the container firing.
|
||||
expect(Scheduler).toHaveYielded(['container2 mouse over']);
|
||||
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
it('hydrates the last target path first for continuous events', async () => {
|
||||
let suspend = false;
|
||||
let resolve;
|
||||
|
||||
+17
-45
@@ -267,31 +267,6 @@ function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEve
|
||||
targetContainer: EventTarget,
|
||||
nativeEvent: AnyNativeEvent,
|
||||
) {
|
||||
// TODO: replaying capture phase events is currently broken
|
||||
// because we used to do it during top-level native bubble handlers
|
||||
// but now we use different bubble and capture handlers.
|
||||
// In eager mode, we attach capture listeners early, so we need
|
||||
// to filter them out until we fix the logic to handle them correctly.
|
||||
const allowReplay = (eventSystemFlags & IS_CAPTURE_PHASE) === 0;
|
||||
|
||||
if (
|
||||
allowReplay &&
|
||||
hasQueuedDiscreteEvents() &&
|
||||
isDiscreteEventThatRequiresHydration(domEventName)
|
||||
) {
|
||||
// If we already have a queue of discrete events, and this is another discrete
|
||||
// event, then we can't dispatch it regardless of its target, since they
|
||||
// need to dispatch in order.
|
||||
queueDiscreteEvent(
|
||||
null, // Flags that we're not actually blocked on anything as far as we know.
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
targetContainer,
|
||||
nativeEvent,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let blockedOn = findInstanceBlockingEvent(
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
@@ -306,28 +281,25 @@ function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEve
|
||||
return_targetInst,
|
||||
targetContainer,
|
||||
);
|
||||
if (allowReplay) {
|
||||
clearIfContinuousEvent(domEventName, nativeEvent);
|
||||
}
|
||||
clearIfContinuousEvent(domEventName, nativeEvent);
|
||||
return;
|
||||
}
|
||||
|
||||
if (allowReplay) {
|
||||
if (
|
||||
queueIfContinuousEvent(
|
||||
blockedOn,
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
targetContainer,
|
||||
nativeEvent,
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// We need to clear only if we didn't queue because
|
||||
// queueing is accumulative.
|
||||
clearIfContinuousEvent(domEventName, nativeEvent);
|
||||
if (
|
||||
queueIfContinuousEvent(
|
||||
blockedOn,
|
||||
domEventName,
|
||||
eventSystemFlags,
|
||||
targetContainer,
|
||||
nativeEvent,
|
||||
)
|
||||
) {
|
||||
nativeEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
// We need to clear only if we didn't queue because
|
||||
// queueing is accumulative.
|
||||
clearIfContinuousEvent(domEventName, nativeEvent);
|
||||
|
||||
if (
|
||||
eventSystemFlags & IS_CAPTURE_PHASE &&
|
||||
@@ -358,10 +330,10 @@ function dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEve
|
||||
}
|
||||
blockedOn = nextBlockedOn;
|
||||
}
|
||||
if (blockedOn) {
|
||||
if (blockedOn !== null) {
|
||||
nativeEvent.stopPropagation();
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// This is not replayable so we'll invoke it but without a target,
|
||||
|
||||
+22
-9
@@ -472,15 +472,26 @@ function attemptReplayContinuousQueuedEvent(
|
||||
queuedEvent.nativeEvent,
|
||||
);
|
||||
if (nextBlockedOn === null) {
|
||||
setReplayingEvent(queuedEvent.nativeEvent);
|
||||
dispatchEventForPluginEventSystem(
|
||||
queuedEvent.domEventName,
|
||||
queuedEvent.eventSystemFlags,
|
||||
queuedEvent.nativeEvent,
|
||||
return_targetInst,
|
||||
targetContainer,
|
||||
);
|
||||
resetReplayingEvent();
|
||||
if (enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay) {
|
||||
const nativeEvent = queuedEvent.nativeEvent;
|
||||
const nativeEventClone = new nativeEvent.constructor(
|
||||
nativeEvent.type,
|
||||
(nativeEvent: any),
|
||||
);
|
||||
setReplayingEvent(nativeEventClone);
|
||||
nativeEvent.target.dispatchEvent(nativeEventClone);
|
||||
resetReplayingEvent();
|
||||
} else {
|
||||
setReplayingEvent(queuedEvent.nativeEvent);
|
||||
dispatchEventForPluginEventSystem(
|
||||
queuedEvent.domEventName,
|
||||
queuedEvent.eventSystemFlags,
|
||||
queuedEvent.nativeEvent,
|
||||
return_targetInst,
|
||||
targetContainer,
|
||||
);
|
||||
resetReplayingEvent();
|
||||
}
|
||||
} else {
|
||||
// We're still blocked. Try again later.
|
||||
const fiber = getInstanceFromNode(nextBlockedOn);
|
||||
@@ -532,6 +543,8 @@ function replayUnblockedEvents() {
|
||||
nextDiscreteEvent.nativeEvent,
|
||||
);
|
||||
if (nextBlockedOn === null) {
|
||||
// This whole function is in !enableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay,
|
||||
// so we don't need the new replay behavior code branch.
|
||||
setReplayingEvent(nextDiscreteEvent.nativeEvent);
|
||||
dispatchEventForPluginEventSystem(
|
||||
nextDiscreteEvent.domEventName,
|
||||
|
||||
Reference in New Issue
Block a user