UNPKG

4.71 kBPlain TextView Raw
1'use strict';
2import { reportFatalErrorOnJS } from './errors';
3import { isChromeDebugger, isJest, shouldBeUseWeb } from './PlatformChecker';
4import {
5 runOnJS,
6 setupMicrotasks,
7 callMicrotasks,
8 runOnUIImmediately,
9} from './threads';
10import { mockedRequestAnimationFrame } from './mockedRequestAnimationFrame';
11
12const IS_JEST = isJest();
13const SHOULD_BE_USE_WEB = shouldBeUseWeb();
14const IS_CHROME_DEBUGGER = isChromeDebugger();
15
16// callGuard is only used with debug builds
17export function callGuardDEV<Args extends unknown[], ReturnValue>(
18 fn: (...args: Args) => ReturnValue,
19 ...args: Args
20): ReturnValue | void {
21 'worklet';
22 try {
23 return fn(...args);
24 } catch (e) {
25 if (global.__ErrorUtils) {
26 global.__ErrorUtils.reportFatalError(e as Error);
27 } else {
28 throw e;
29 }
30 }
31}
32
33export function setupCallGuard() {
34 'worklet';
35 global.__callGuardDEV = callGuardDEV;
36 global.__ErrorUtils = {
37 reportFatalError: (error: Error) => {
38 runOnJS(reportFatalErrorOnJS)({
39 message: error.message,
40 stack: error.stack,
41 });
42 },
43 };
44}
45
46// We really have to create a copy of console here. Function runOnJS we use on elements inside
47// this object makes it not configurable
48const capturableConsole = { ...console };
49
50export function setupConsole() {
51 'worklet';
52 if (!IS_CHROME_DEBUGGER) {
53 // @ts-ignore TypeScript doesn't like that there are missing methods in console object, but we don't provide all the methods for the UI runtime console version
54 global.console = {
55 /* eslint-disable @typescript-eslint/unbound-method */
56 assert: runOnJS(capturableConsole.assert),
57 debug: runOnJS(capturableConsole.debug),
58 log: runOnJS(capturableConsole.log),
59 warn: runOnJS(capturableConsole.warn),
60 error: runOnJS(capturableConsole.error),
61 info: runOnJS(capturableConsole.info),
62 /* eslint-enable @typescript-eslint/unbound-method */
63 };
64 }
65}
66
67function setupRequestAnimationFrame() {
68 'worklet';
69
70 // Jest mocks requestAnimationFrame API and it does not like if that mock gets overridden
71 // so we avoid doing requestAnimationFrame batching in Jest environment.
72 const nativeRequestAnimationFrame = global.requestAnimationFrame;
73
74 let animationFrameCallbacks: Array<(timestamp: number) => void> = [];
75 let lastNativeAnimationFrameTimestamp = -1;
76
77 global.__flushAnimationFrame = (frameTimestamp: number) => {
78 const currentCallbacks = animationFrameCallbacks;
79 animationFrameCallbacks = [];
80 currentCallbacks.forEach((f) => f(frameTimestamp));
81 callMicrotasks();
82 };
83
84 global.requestAnimationFrame = (
85 callback: (timestamp: number) => void
86 ): number => {
87 animationFrameCallbacks.push(callback);
88 if (animationFrameCallbacks.length === 1) {
89 // We schedule native requestAnimationFrame only when the first callback
90 // is added and then use it to execute all the enqueued callbacks. Once
91 // the callbacks are run, we clear the array.
92 nativeRequestAnimationFrame((timestamp) => {
93 if (lastNativeAnimationFrameTimestamp >= timestamp) {
94 // Make sure we only execute the callbacks once for a given frame
95 return;
96 }
97 lastNativeAnimationFrameTimestamp = timestamp;
98 global.__frameTimestamp = timestamp;
99 global.__flushAnimationFrame(timestamp);
100 global.__frameTimestamp = undefined;
101 });
102 }
103 // Reanimated currently does not support cancelling callbacks requested with
104 // requestAnimationFrame. We return -1 as identifier which isn't in line
105 // with the spec but it should give users better clue in case they actually
106 // attempt to store the value returned from rAF and use it for cancelling.
107 return -1;
108 };
109}
110
111export function initializeUIRuntime() {
112 if (IS_JEST) {
113 // requestAnimationFrame react-native jest's setup is incorrect as it polyfills
114 // the method directly using setTimeout, therefore the callback doesn't get the
115 // expected timestamp as the only argument: https://github.com/facebook/react-native/blob/main/packages/react-native/jest/setup.js#L28
116 // We override this setup here to make sure that callbacks get the proper timestamps
117 // when executed. For non-jest environments we define requestAnimationFrame in setupRequestAnimationFrame
118 // @ts-ignore TypeScript uses Node definition for rAF, setTimeout, etc which returns a Timeout object rather than a number
119 globalThis.requestAnimationFrame = mockedRequestAnimationFrame;
120 }
121
122 runOnUIImmediately(() => {
123 'worklet';
124 setupCallGuard();
125 setupConsole();
126 if (!SHOULD_BE_USE_WEB) {
127 setupMicrotasks();
128 setupRequestAnimationFrame();
129 }
130 })();
131}