Skip to content

Commit 0220f92

Browse files
gaearonlunaruan
authored andcommitted
Fix ignored setState in Safari when iframe is touched (#24459)
1 parent 6266263 commit 0220f92

File tree

6 files changed

+73
-18
lines changed

6 files changed

+73
-18
lines changed

packages/react-devtools-shared/src/__tests__/utils-test.js

+21
Original file line numberDiff line numberDiff line change
@@ -189,5 +189,26 @@ describe('utils', () => {
189189
]);
190190
expect(formatWithStyles(['%%c%c'], 'color: gray')).toEqual(['%%c%c']);
191191
});
192+
193+
it('should format non string inputs as the first argument', () => {
194+
expect(formatWithStyles([{foo: 'bar'}])).toEqual([{foo: 'bar'}]);
195+
expect(formatWithStyles([[1, 2, 3]])).toEqual([[1, 2, 3]]);
196+
expect(formatWithStyles([{foo: 'bar'}], 'color: gray')).toEqual([
197+
'%c%o',
198+
'color: gray',
199+
{foo: 'bar'},
200+
]);
201+
expect(formatWithStyles([[1, 2, 3]], 'color: gray')).toEqual([
202+
'%c%o',
203+
'color: gray',
204+
[1, 2, 3],
205+
]);
206+
expect(formatWithStyles([{foo: 'bar'}, 'hi'], 'color: gray')).toEqual([
207+
'%c%o %s',
208+
'color: gray',
209+
{foo: 'bar'},
210+
'hi',
211+
]);
212+
});
192213
});
193214
});

packages/react-devtools-shared/src/backend/utils.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -181,17 +181,16 @@ export function formatWithStyles(
181181
inputArgs === undefined ||
182182
inputArgs === null ||
183183
inputArgs.length === 0 ||
184-
typeof inputArgs[0] !== 'string' ||
185184
// Matches any of %c but not %%c
186-
inputArgs[0].match(/([^%]|^)(%c)/g) ||
185+
(typeof inputArgs[0] === 'string' && inputArgs[0].match(/([^%]|^)(%c)/g)) ||
187186
style === undefined
188187
) {
189188
return inputArgs;
190189
}
191190

192191
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
193192
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
194-
if (inputArgs[0].match(REGEXP)) {
193+
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
195194
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
196195
} else {
197196
const firstArg = inputArgs.reduce((formatStr, elem, i) => {

packages/react-devtools-shared/src/hook.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -180,17 +180,17 @@ export function installHook(target: any): DevToolsHook | null {
180180
inputArgs === undefined ||
181181
inputArgs === null ||
182182
inputArgs.length === 0 ||
183-
typeof inputArgs[0] !== 'string' ||
184183
// Matches any of %c but not %%c
185-
inputArgs[0].match(/([^%]|^)(%c)/g) ||
184+
(typeof inputArgs[0] === 'string' &&
185+
inputArgs[0].match(/([^%]|^)(%c)/g)) ||
186186
style === undefined
187187
) {
188188
return inputArgs;
189189
}
190190

191191
// Matches any of %(o|O|d|i|s|f), but not %%(o|O|d|i|s|f)
192192
const REGEXP = /([^%]|^)((%%)*)(%([oOdisf]))/g;
193-
if (inputArgs[0].match(REGEXP)) {
193+
if (typeof inputArgs[0] === 'string' && inputArgs[0].match(REGEXP)) {
194194
return [`%c${inputArgs[0]}`, style, ...inputArgs.slice(1)];
195195
} else {
196196
const firstArg = inputArgs.reduce((formatStr, elem, i) => {

packages/react-dom/src/__tests__/ReactDOMSafariMicrotaskBug-test.js

+35-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ let act;
1616

1717
describe('ReactDOMSafariMicrotaskBug-test', () => {
1818
let container;
19-
let simulateSafariBug;
19+
let flushMicrotasksPrematurely;
2020

2121
beforeEach(() => {
2222
// In Safari, microtasks don't always run on clean stack.
@@ -27,9 +27,12 @@ describe('ReactDOMSafariMicrotaskBug-test', () => {
2727
window.queueMicrotask = function(cb) {
2828
queue.push(cb);
2929
};
30-
simulateSafariBug = function() {
31-
queue.forEach(cb => cb());
32-
queue = [];
30+
flushMicrotasksPrematurely = function() {
31+
while (queue.length > 0) {
32+
const prevQueue = queue;
33+
queue = [];
34+
prevQueue.forEach(cb => cb());
35+
}
3336
};
3437

3538
jest.resetModules();
@@ -45,7 +48,7 @@ describe('ReactDOMSafariMicrotaskBug-test', () => {
4548
document.body.removeChild(container);
4649
});
4750

48-
it('should be resilient to buggy queueMicrotask', async () => {
51+
it('should deal with premature microtask in commit phase', async () => {
4952
let ran = false;
5053
function Foo() {
5154
const [state, setState] = React.useState(0);
@@ -55,7 +58,7 @@ describe('ReactDOMSafariMicrotaskBug-test', () => {
5558
if (!ran) {
5659
ran = true;
5760
setState(1);
58-
simulateSafariBug();
61+
flushMicrotasksPrematurely();
5962
}
6063
}}>
6164
{state}
@@ -68,4 +71,30 @@ describe('ReactDOMSafariMicrotaskBug-test', () => {
6871
});
6972
expect(container.textContent).toBe('1');
7073
});
74+
75+
it('should deal with premature microtask in event handler', async () => {
76+
function Foo() {
77+
const [state, setState] = React.useState(0);
78+
return (
79+
<button
80+
onClick={() => {
81+
setState(1);
82+
flushMicrotasksPrematurely();
83+
}}>
84+
{state}
85+
</button>
86+
);
87+
}
88+
const root = ReactDOMClient.createRoot(container);
89+
await act(async () => {
90+
root.render(<Foo />);
91+
});
92+
expect(container.textContent).toBe('0');
93+
await act(async () => {
94+
container.firstChild.dispatchEvent(
95+
new MouseEvent('click', {bubbles: true}),
96+
);
97+
});
98+
expect(container.textContent).toBe('1');
99+
});
71100
});

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -835,9 +835,12 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
835835
// https://github.com/facebook/react/issues/22459
836836
// We don't support running callbacks in the middle of render
837837
// or commit so we need to check against that.
838-
if (executionContext === NoContext) {
839-
// It's only safe to do this conditionally because we always
840-
// check for pending work before we exit the task.
838+
if (
839+
(executionContext & (RenderContext | CommitContext)) ===
840+
NoContext
841+
) {
842+
// Note that this would still prematurely flush the callbacks
843+
// if this happens outside render or commit phase (e.g. in an event).
841844
flushSyncCallbacks();
842845
}
843846
});

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

+6-3
Original file line numberDiff line numberDiff line change
@@ -835,9 +835,12 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
835835
// https://github.com/facebook/react/issues/22459
836836
// We don't support running callbacks in the middle of render
837837
// or commit so we need to check against that.
838-
if (executionContext === NoContext) {
839-
// It's only safe to do this conditionally because we always
840-
// check for pending work before we exit the task.
838+
if (
839+
(executionContext & (RenderContext | CommitContext)) ===
840+
NoContext
841+
) {
842+
// Note that this would still prematurely flush the callbacks
843+
// if this happens outside render or commit phase (e.g. in an event).
841844
flushSyncCallbacks();
842845
}
843846
});

0 commit comments

Comments
 (0)